Compare commits

...

141 Commits

Author SHA1 Message Date
3056caf6e3 add config variable for emtpy workspaces 2023-02-21 17:23:13 +01:00
3dff3d8b13 show empty workspaces 2023-02-21 17:23:13 +01:00
Orestis Floros
a5da4d54f3
GitHub Actions: revert changes and use if on each step (#5393) 2023-01-23 19:50:28 +01:00
Orestis Floros
3702960a87
Actions: Fix BASENAME env vars (#5392) 2023-01-23 18:57:08 +01:00
Orestis Floros
6911c116e7
GitHub Actions: push artifacts only on next branch (#5388) 2023-01-23 17:39:55 +01:00
Orestis Floros
bfbe73f665
Merge pull request #5390 from nikitabobko/bobko/userguide
Fix "default binding" mistake in userguide
2023-01-23 13:20:31 +01:00
Nikita Bobko
0f64420281 Fix "default binding" mistake in userguide
Default binding to move window down is $mod+Shift+k, not $mod+Shift+j.
Proof: https://github.com/i3/i3/blob/next/etc/config#L45
This commit reverts this pull request https://github.com/i3/i3/pull/4146
2023-01-23 11:49:37 +01:00
Orestis Floros
26990d90f2
Merge pull request #4311 from i3/i3bar-ws-protocol
i3bar: Add protocol for workspace buttons
2023-01-22 19:05:01 +01:00
Orestis Floros
ba1f40f45f
i3bar: Add protocol for workspace buttons
Closes #3818 (parent issue)
Fixes #1808
Fixes #2333
Fixes #2617
Fixes #3548
2023-01-22 18:59:58 +01:00
Orestis Floros
c52f13900d
Add focus workspace command 2023-01-22 18:33:23 +01:00
Michael Stapelberg
8d64937054
Bump -D_FORTIFY_SOURCE=2 to -D_FORTIFY_SOURCE=3 (#5379)
Arch Linux is discussing increasing to 3, so we should probably keep up:

https://gitlab.archlinux.org/archlinux/rfcs/-/merge_requests/17
2023-01-19 08:37:46 +01:00
Orestis Floros
9c8746c00f
Merge pull request #5355 from orestisfl/5346/do-not-canonicalize-nonprimary
Do not canonicalize "nonprimary" output for i3bar
2023-01-07 10:02:45 +01:00
Orestis Floros
fa25afedd2
Do not canonicalize "nonprimary" output for i3bar
Fixes #5346
2023-01-06 22:30:56 +01:00
Michael Stapelberg
46de32eedd
GitHub Actions: remove i386 autobuild packages (#5345)
They are newly failing since the previous commit (upgrading to Ubuntu focal),
so instead of debugging what the issue is, let’s just remove them entirely.
Not many i386 users are left, as the world is on amd64 and arm64 these days.
2023-01-02 12:24:54 +01:00
Michael Stapelberg
944a262688
GitHub Actions: build Ubuntu packages using Ubuntu focal (#5344)
This is required to satisfy our meson.build minimal Meson version.
2023-01-02 12:10:36 +01:00
Michael Stapelberg
aaee2b3eae free some heap allocations to satisfy LeakSanitizer 2023-01-02 11:36:37 +01:00
Michael Stapelberg
dfb3850989 fix reload binding memory issue: copy current_binding_mode 2023-01-02 11:36:37 +01:00
Michael Stapelberg
90d7b9769c meson: specify check: false on run_command
We use run_command for conditionals, meaning meson execution should not stop
when the command returns false. This change keeps our meson setup working
throughout the upcoming change of default behavior (check: true).
2023-01-02 11:36:37 +01:00
Michael Stapelberg
16f83396b4 GitHub Actions: switch to meson setup subcommand
Using “meson” instead of “meson setup” results in a warning.
2023-01-02 11:36:37 +01:00
Michael Stapelberg
8fe28d1a95 fix -Wmaybe-unused and -Wstringop-truncation warnings 2023-01-02 11:36:37 +01:00
Michael Stapelberg
d06e87eb8d GitHub Actions: build with -D_FORTIFY_SOURCE=2
This requires --buildtype=debugoptimized (or --buildtype=release, but
optimizations need to be enabled), and will allow us to keep the i3 build free
of warnings during development.

Distributions like Debian build with -D_FORTIFY_SOURCE=2.
2023-01-02 11:36:37 +01:00
Michael Stapelberg
3e184daf29 release.sh: update after 4.22 release 2023-01-02 11:36:37 +01:00
Michael Stapelberg
7d3a3ae0fb clean up old release notes 2023-01-02 09:49:20 +01:00
Michael Stapelberg
6984dff01a debian: update changelog 2023-01-02 09:46:32 +01:00
Michael Stapelberg
47b2caa116 Merge branch 'release-4.22' 2023-01-02 09:39:12 +01:00
Michael Stapelberg
57f984ae67 Restore non-git version suffix 2023-01-02 09:39:12 +01:00
Michael Stapelberg
b85da284a7 release i3 4.22 2023-01-02 09:39:00 +01:00
Michael Stapelberg
ab6f1fd160
fix focus <direction> with negative gaps (#5333)
fixes #5293
2022-12-21 08:11:51 +01:00
Orestis Floros
ed690c7ba0
Merge pull request #5324 from orestisfl/5323/mode-in-binding-event
Add "mode" field in binding event
2022-12-14 14:24:05 +01:00
Orestis Floros
d5c8319b6c
Add "mode" field in binding event
This does *not* go in the binding object to reflect the same hierarchy
of the config file: a mode is a collection of bindings.

Fixes #5323
2022-12-14 13:23:12 +01:00
Demian
1786b13f0d
i3-dmenu-desktop: Allow more than one --entry-type (#5294)
Unlike in the man page, only one --entry-type is reasonable possible.
On using multiple --entry-types and a command offers multiple, duplicates are removed i3-dmenu-desktop.
See more at #5291

added --show-duplicates flag for this
2022-12-06 17:23:10 +01:00
Michael Stapelberg
fd95a47183
Update to clang-format-12 (as 10 is no longer installable) (#5317)
No changes to the code are needed.
2022-12-06 11:06:18 +01:00
Orestis Floros
30131ed697
Support nonprimary output keyword (#5273)
Fixes #4878
2022-11-19 18:11:26 +01:00
Orestis Floros
a1e4b44955
command.spec: Put cmd_border stuff together (#5266) 2022-11-13 16:22:41 +01:00
Orestis Floros
029cb8af19
Use mask to determine workspace gaps assignments (#5283)
Fixes #5282
2022-11-13 16:03:58 +01:00
Orestis Floros
60c3fedb73
window_update_motif_hints: Do not assert that the property will always be there (#5281)
Fixes #5280
2022-11-13 10:48:26 +01:00
Michael Stapelberg
96614a2f32
apply updated workspace gap assignments after reload (#5279)
Fixes https://github.com/i3/i3/issues/5257
2022-11-12 17:32:30 +01:00
Michael Stapelberg
1ba0eaca22
Make floating_from and tiling_from criterion work in commands, too (#5278)
Fixes https://github.com/i3/i3/issues/5258
2022-11-12 16:44:08 +01:00
Michael Stapelberg
2ac6180b90
gaps: position graphical resize bar in the middle between windows (#5277)
Fixes https://github.com/i3/i3/issues/5256
2022-11-12 15:48:07 +01:00
Michael Stapelberg
170a322cc2
fix: prevent gaps inside floating split containers (#5276)
Fixes https://github.com/i3/i3/issues/5272
2022-11-12 14:58:13 +01:00
Michael Stapelberg
d130126204
gaps: fix inner gaps for stacked/tabbed containers in splith/splitv (#5275)
Fixes https://github.com/i3/i3/issues/5261
2022-11-12 14:08:13 +01:00
Michael Stapelberg
2b236955bd
use con_border_style() to fix titles in stacked/tabbed containers (#5274)
Previously, the code was directly accessing con->border_style, which circumvents
the special-casing for stacked/tabbed containers that forces window titles even
for title-less containers.

Fixes https://github.com/i3/i3/issues/5269
2022-11-12 12:43:55 +01:00
Orestis Floros
be27a2f50d
userguide: gaps: mention minimum version (#5265) 2022-11-08 17:14:22 +01:00
Michael Stapelberg
804bca3a9a
gaps: make workspace gap assignments order-independent (#5259)
This commit moves subtracting the global gaps from the workspace gaps:
previously, this calculation was done while parsing the configuration
(order dependent), now it’s done at workspace assignment evaluation time.

related to https://github.com/i3/i3/issues/3724

fixes https://github.com/i3/i3/issues/5253
2022-11-07 19:02:57 +01:00
Michael Stapelberg
14795c303c
fix title bar rendering with hide_edge_borders smart (#5260)
related to https://github.com/i3/i3/pull/5245

fixes https://github.com/i3/i3/issues/5254
2022-11-07 19:01:58 +01:00
Michael Stapelberg
e6a28b9475
gaps: change workspace rendering to fix sizes with large outer gaps (#5252)
Currently, containers only consider their neighbors and screen edges.

If >2 containers are in a line, the outer containers adjust from outer gaps, but
the middle containers know nothing of this and only consider the inner gaps.

When the outer gaps differ substantially from the inner gaps, the left-most and
right-most containers are smaller as only they adjust for the larger outer gaps.

This commit changes the rendering: containers are now always inset by their
inner gap settings, and workspace containers is now inset by the outer gap
settings.

The result is that many tiled containers have the same size, and the gaps
overall work as the user might expect them to previous combinations of
outer/inner gap settings still produce the same result, albeit with fixed
outer-most sizes.

fixes https://github.com/Airblader/i3/issues/22

related to https://github.com/i3/i3/issues/3724

Co-authored-by: Cameron Leger <contact@cameronleger.com>
2022-11-06 21:22:21 +01:00
Michael Stapelberg
69e13d7821
Revert "gaps: use logical_px() to work correctly on hi-dpi monitors" (#5251)
This reverts commit 6b658f88be50d5251f85eaf6f838d6c67ebaac95.

The commit was misguided: the pixel values are already run through logical_px()
when parsing the configuration directive or command, so they should not be run
through another logical_px() pass at rendering time.
2022-11-06 19:42:30 +01:00
Michael Stapelberg
2a91514a31 t/548-motif-hints: add missing $x->flush after $x->change_property
I noticed the test was flaky before, possibly this fixes it.

related to #3009
2022-11-06 18:18:23 +01:00
Orestis Floros
9dcf37b428 Fix motif behavior according to spec
See https://linux.die.net/man/3/vendorshell
The important part is:
> MWM_DECOR_ALL
> All decorations *except* those specified by other flag bits that are set
2022-11-06 18:18:23 +01:00
Michael Stapelberg
9e3a9e8225
Allow text drawing to use the alpha channel. (#5246)
This is the last remaining diff from the i3-gaps tree.

related to https://github.com/i3/i3/issues/3724

Tested using the following config with picom:

bar {
	i3bar_command i3bar -t
	status_command i3status
	colors {
		# fully	transparent text on opaque background:
		statusline #ffffff00
		background #000000ff
	}
}
2022-11-06 12:43:37 +01:00
Michael Stapelberg
c8fd8eff21 userguide: document smart_borders 2022-11-06 12:43:01 +01:00
Michael Stapelberg
6fe625f469 userguide: document hide_edge_borders smart_no_gaps
related to https://github.com/i3/i3/issues/3724
2022-11-06 12:43:01 +01:00
Michael Stapelberg
d26ddcbfe5 draw leaf window decorations on ->frame instead of ->parent->frame
related to https://github.com/i3/i3/issues/3724
fixes https://github.com/i3/i3/issues/1966
2022-11-05 15:58:15 +01:00
Michael Stapelberg
6e6af01b7a draw_util: refactor surface_initialized macro into function
This makes it possible to set a breakpoint in gdb on a line in the function and
get a backtrace of un-initialized surface access.
2022-11-05 15:58:15 +01:00
Michael Stapelberg
a59423df81
check-spelling: add another false positive (#5247) 2022-11-05 15:56:36 +01:00
Michael Stapelberg
2bfa06b7df
meson.build: include new gaps1920.png file in dist tarball (#5242) 2022-11-01 18:26:57 +01:00
Michael Stapelberg
9c9b110ed1 config.spec: add missing smart_gaps inverse_outer comment 2022-11-01 17:55:46 +01:00
Michael Stapelberg
588cc4c79c refactor cmd_gaps to get rid of all #define
I’m still not 100% happy with how the function turned out (it still does too
many things at once), but this seems like an improvement — at least reading and
navigating the code with LSP now works better.
2022-11-01 17:55:46 +01:00
Michael Stapelberg
4d0323fa9e t/319-gaps.t: also test the gaps command 2022-11-01 17:55:46 +01:00
Michael Stapelberg
3f400b8ad0 move gaps-specific logic out of con.c and render.c into gaps.c 2022-11-01 17:55:46 +01:00
Michael Stapelberg
a5791b2e64 gaps: allow optional px suffix 2022-11-01 17:55:46 +01:00
Michael Stapelberg
b2c696f680 add basic gaps test 2022-11-01 17:55:46 +01:00
Michael Stapelberg
b82b3e85da userguide: document gaps config directive and gaps command 2022-11-01 17:55:46 +01:00
Michael Stapelberg
6b658f88be gaps: use logical_px() to work correctly on hi-dpi monitors 2022-11-01 17:55:46 +01:00
Michael Stapelberg
5b0f848a40 Fix config.spec comment 2022-11-01 17:55:46 +01:00
Michael Stapelberg
9ac027234b refactor render_con() global parameter into should_inset_con()
This bundles the logic all in one place and thereby makes it a little easier to
understand.
2022-11-01 17:55:46 +01:00
Michael Stapelberg
2fbb36b95f remove dead code: window_rect is overwritten a few lines below 2022-11-01 17:55:46 +01:00
Michael Stapelberg
b825dc124a Merge gaps support as-is
This code was copied over unmodified from https://github.com/Airblader/i3-gaps.

I have split out the differences between i3-gaps and i3 into three areas:
1. Gaps
2. i3bar height
3. rgba colors
2022-11-01 17:55:46 +01:00
Michael Stapelberg
0b89d4b2a7 implement bar { padding } config directive
related to https://github.com/i3/i3/issues/3724
related to https://github.com/i3/i3/pull/4288
fixes https://github.com/i3/i3/issues/3721
2022-10-30 22:22:08 +01:00
Michael Stapelberg
327bca26d8 add test for bar { height } 2022-10-30 22:22:08 +01:00
Michael Stapelberg
c45342e74f Merge support for the bar { height } option as-is from i3-gaps
related to https://github.com/i3/i3/issues/3724
related to https://github.com/i3/i3/issues/3721

In a follow-up commit, we can evolve this into the padding directive as
discussed on issue #3721.
2022-10-30 22:22:08 +01:00
Michael Stapelberg
62eb0033b1 docs/userguide: fix asciidoc block syntax (for asciidoctor)
asciidoctor is a bit stricter in what it accepts: the leading and trailing lines
need to have the exact same number of characters, and apparently there needs to
be a blank line after the trailing delimiter line.
2022-10-30 22:22:08 +01:00
Michael Stapelberg
a68eb3a71e delete old release notes 2022-10-30 22:22:08 +01:00
Orestis Floros
080c73d1a4
i3-dmenu-desktop: ignore SIGPIPE when writing to dmenu (#5228)
Fixes broken test
2022-10-29 08:20:09 +02:00
Michael Stapelberg
1f53ae4614 debian: update changelog 2022-10-24 21:33:43 +02:00
Michael Stapelberg
23bc304477 Update debian/changelog 2022-10-24 21:23:07 +02:00
Michael Stapelberg
c6bfd05276 Merge branch 'stable' into next 2022-10-24 21:23:07 +02:00
Michael Stapelberg
85252a3bd1 Merge branch 'release-4.21.1' 2022-10-24 21:23:06 +02:00
Michael Stapelberg
9ffcc51183 Restore non-git version suffix 2022-10-24 21:23:06 +02:00
Michael Stapelberg
39afa033e4 release i3 4.21.1 2022-10-24 21:22:50 +02:00
Michael Stapelberg
3b9d70af41 tiling drag: only start when there are drop targets (#5213)
This prevents potentially confusing drag & drop on fullscreen containers and
only-containers on workspaces.

fixes https://github.com/i3/i3/issues/5184
2022-10-24 21:13:03 +02:00
Michael Stapelberg
c55b52a7cc tiling drag: ignore scratchpad windows when locating drop targets (#5211)
fixes https://github.com/i3/i3/issues/5170
2022-10-24 21:13:00 +02:00
Michael Stapelberg
131b0c5b3d make tiling drag configurable
fixes https://github.com/i3/i3/issues/5155
2022-10-24 21:12:58 +02:00
Michael Stapelberg
aa876585e8 tiling drag: left-click needs threshold, mod-click doesn’t
related to https://github.com/i3/i3/issues/5155
2022-10-24 21:12:56 +02:00
Michael Stapelberg
f1754e12c0 increase drag threshold, run it through logical_px()
related to https://github.com/i3/i3/issues/5155
2022-10-24 21:12:53 +02:00
Michael Stapelberg
e12d2f6a1d tiling drag: fix cursor (wrong argument passed) (#5207)
Currently, the cursor is XCURSOR_CURSOR_TOP_RIGHT_CORNER (== BORDER_TOP),
but the intended cursor was XCURSOR_CURSOR_MOVE.

noticed this as part of https://github.com/i3/i3/issues/5198
2022-10-24 21:12:51 +02:00
Michael Stapelberg
b88ca36a5a tiling drag: allow click immediately, to focus on decoration click (#5206)
With the introduction of tiling drag, i3’s click behavior in window decorations
changed: before tiling drag, in a tabbed or stacked container, a window would be
focused/raised on mouse down. After tiling drag, on mouse up.

This commit sends XCB_ALLOW_REPLAY_POINTER before running the tiling drag code,
thereby restoring the focus/raise-on-mouse-down behavior without affecting the
tiling drag operation.

fixes https://github.com/i3/i3/issues/5169
2022-10-24 21:12:48 +02:00
bodea
7abd58abf2 Escape ~ to prevent interpretation as subscript. (#5168) 2022-10-24 21:12:43 +02:00
Matias Goldfeld
d62183a2b8 Fix segfault during config validation (#5167) (#5173)
Configs with bar blocks will segfault during validation since they
copy the i3 font which is not set during validation.
2022-10-24 21:12:38 +02:00
Tudor Brindus
9d6a8735eb Raise floating windows when their border is clicked (#5196)
This logic already existed for `floating_drag_window`, but we need it
for `floating_resize_window` too.

Fixes #5195.
2022-10-24 21:12:30 +02:00
Michael Stapelberg
decc37eba1 Fix i3-dmenu-desktop quoting (#5162)
Commit 70f23caa9a18afc146f696fdf7d2481e5f7f0101 introduced new issues.

Instead of distinguishing " and \, as that commit attempted,
let’s instead keep the level of escaping by escaping each backslash,
just like each double quote.

I tested this with:

    # recommended way to quote $ and " in quoted arguments, not ambiguous
    Exec=/tmp/logargs "hello \\$PWD \\"and\\" more"

    # permitted way to quote $ and " in quoted arguments, but ambiguous
    Exec=/tmp/logargs "hello \$PWD \"and\" more"

    # permitted way to quote arguments, slightly unusual to quote first arg
    Exec="/tmp/logargs" hey

    # a complicated shell expression, not ambiguous
    Exec=sh -c "if [ -n \\"\\$*\\" ]; then exec /tmp/logargs --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\"; else exec /tmp/logargs --alternate-editor= --create-frame; fi" placeholder %F

related to https://github.com/i3/i3/issues/4697 (electrum, original)
related to https://github.com/i3/i3/issues/5152 (phpstorm, breakage)
related to https://github.com/i3/i3/issues/5156 (emacsclient, breakage)
2022-10-24 21:12:27 +02:00
Orestis Floros
3f58d51ec6 Fix motif logic for new floats
- manage.c still used wrong `motif_border_style == BS_NORMAL`
- container must be set to floating first for correct code path and
  correct max_user_border_style to be used in con_set_border_style
- Motif test now includes default_floating_border
2022-10-24 21:12:25 +02:00
Orestis Floros
304e815ed4 Motif hints: Respect maximum border style configuration set by user
Context:
Motif hints [1] allow applications to request specific window manager
frame decorations. Most applications like alacritty, chromium, and
godot, use the hints as a binary flag, setting or un-setting
`MWM_DECOR_ALL`.
Previously [2], we had disallowed applications to set the "normal"
border style through motif hints. This effectively meant that users that
had set `default_border pixel` would not see applications spawning with
normal decorations [3].
However, that meant that applications like godot [4] could not toggle
their border between none and normal so the behaviour changed with
v4.21 [5].
That change however also allowed applications to override the default
none/pixel border style the user set. For example, alacritty can be
configured to either have all or no decorations [6] and they always set
the motif hint on startup, completely overriding i3 user's preference:
1. If decorations are disabled with alacritty's config then they will
   override `default_border normal` and no title will be used.
2. If decorations are enabled (also the default behavior) with
   alacritty's config then they will override `default_border pixel` and
   a title will be used.

This patch redefines how we interpret motif hints. When a client sets
`MWM_DECOR_ALL`, we interpret it as "the maximum decoration the user has
allowed for this window". I.e., if a client was all decorations and the
user expects the window to not have a title, we don't include the title
in "all" decorations.

The user's preference is determined by these:
1. For new tiling windows, as set by `default_border`
2. For new floating windows, as set by `default_floating_border`
3. For all windows that the user runs the `border` command, whatever is
   the result of that command for that window.
Example:
- User opens new tiling window with `default_border pixel` => maximum
  decoration = PIXEL
- Window requests all/title decorations => i3 enforces the user maximum
  decoration, PIXEL (no change)
- Window requests no decorations => i3 accepts it and sets border to
  NONE, maximum decoration remains PIXEL
- User toggles the border, next style is NORMAL => maximum decoration is
  now NORMAL
- Window requests no decorations => i3 accepts it and sets border to
  NONE
- Window requests all/title decorations => i3 accepts it and sets the
  maximum border, NORMAL
- User toggles the border, next style is NONE => maximum decoration is
  now NONE
- Window requests all/title decorations => i3 enforces the user maximum
  decoration, NONE (no change)

With this, we will still allow behaviour where windows can toggle their
border style with motif hints [4][7].

Reference/footnotes:
[1]: https://linux.die.net/man/3/vendorshell
[2]: https://github.com/i3/i3/pull/2386
[3]: Notice how there is apparently a gap because `default border none`
settings would not be respected if an application wanted just "border"
decorations but this was never reported, probably because of the rare
conjunction of applications requesting that and users defaulting to none
borders.
[4]: https://github.com/godotengine/godot/issues/40037
[5]: https://github.com/i3/i3/pull/5135
[6]: Set by an underlying library here:
fafdedfb7d/src/platform_impl/linux/x11/util/hint.rs (L113-L142)
called by alactitty here:
4ddb608563/alacritty/src/display/window.rs (L341)
[7]: https://github.com/i3/i3/issues/3678

Closes #3678
Fixes #5149
2022-10-24 21:12:23 +02:00
Orestis Floros
0af2bac9ed Order border_style_t enum according to amount of decoration
The only place where this matters is with command `border toggle` which
cycles through them. Luckily, the behaviour does not change because the
order is the same with the new enum.
2022-10-24 21:12:20 +02:00
Erich Heine
5e4ed2fc75 Adds sticky field to get_tree reply in ipc doc 2022-10-24 21:12:00 +02:00
Orestis Floros
de3fc07123
Update actions/checkout to v3 (#5226)
Fixes #5225
2022-10-24 21:01:23 +02:00
Orestis Floros
b18b80ca40
i3-dmenu-desktop test: Do not autostart i3 (#5224)
This actually fixes a hang that happens on my machine for some reason.
Regardless, starting i3 is not necessary for this test.
2022-10-24 19:57:07 +02:00
Michael Stapelberg
5e759ed424
tiling drag: only start when there are drop targets (#5213)
This prevents potentially confusing drag & drop on fullscreen containers and
only-containers on workspaces.

fixes https://github.com/i3/i3/issues/5184
2022-10-18 22:10:03 +02:00
Michael Stapelberg
941229ee62
tiling drag: ignore scratchpad windows when locating drop targets (#5211)
fixes https://github.com/i3/i3/issues/5170
2022-10-16 22:12:45 +02:00
Michael Stapelberg
55d400b17d make tiling drag configurable
fixes https://github.com/i3/i3/issues/5155
2022-10-16 18:21:08 +02:00
Michael Stapelberg
2ba393f084 tiling drag: left-click needs threshold, mod-click doesn’t
related to https://github.com/i3/i3/issues/5155
2022-10-16 18:21:08 +02:00
Michael Stapelberg
6479cb7deb increase drag threshold, run it through logical_px()
related to https://github.com/i3/i3/issues/5155
2022-10-16 18:21:08 +02:00
Michael Stapelberg
8128774386
tiling drag: fix cursor (wrong argument passed) (#5207)
Currently, the cursor is XCURSOR_CURSOR_TOP_RIGHT_CORNER (== BORDER_TOP),
but the intended cursor was XCURSOR_CURSOR_MOVE.

noticed this as part of https://github.com/i3/i3/issues/5198
2022-10-16 16:53:15 +02:00
Michael Stapelberg
a6c86fd794
tiling drag: allow click immediately, to focus on decoration click (#5206)
With the introduction of tiling drag, i3’s click behavior in window decorations
changed: before tiling drag, in a tabbed or stacked container, a window would be
focused/raised on mouse down. After tiling drag, on mouse up.

This commit sends XCB_ALLOW_REPLAY_POINTER before running the tiling drag code,
thereby restoring the focus/raise-on-mouse-down behavior without affecting the
tiling drag operation.

fixes https://github.com/i3/i3/issues/5169
2022-10-16 15:21:22 +02:00
bodea
4f3d4c26f6
Escape ~ to prevent interpretation as subscript. (#5168) 2022-10-11 18:09:26 +02:00
Matias Goldfeld
c5dc0d8c93
Fix segfault during config validation (#5167) (#5173)
Configs with bar blocks will segfault during validation since they
copy the i3 font which is not set during validation.
2022-10-10 18:52:55 +02:00
Tudor Brindus
06e31ece8f
Raise floating windows when their border is clicked (#5196)
This logic already existed for `floating_drag_window`, but we need it
for `floating_resize_window` too.

Fixes #5195.
2022-10-10 14:22:55 +02:00
Michael Stapelberg
812ec43d46
Fix i3-dmenu-desktop quoting (#5162)
Commit 70f23caa9a18afc146f696fdf7d2481e5f7f0101 introduced new issues.

Instead of distinguishing " and \, as that commit attempted,
let’s instead keep the level of escaping by escaping each backslash,
just like each double quote.

I tested this with:

    # recommended way to quote $ and " in quoted arguments, not ambiguous
    Exec=/tmp/logargs "hello \\$PWD \\"and\\" more"

    # permitted way to quote $ and " in quoted arguments, but ambiguous
    Exec=/tmp/logargs "hello \$PWD \"and\" more"

    # permitted way to quote arguments, slightly unusual to quote first arg
    Exec="/tmp/logargs" hey

    # a complicated shell expression, not ambiguous
    Exec=sh -c "if [ -n \\"\\$*\\" ]; then exec /tmp/logargs --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\"; else exec /tmp/logargs --alternate-editor= --create-frame; fi" placeholder %F

related to https://github.com/i3/i3/issues/4697 (electrum, original)
related to https://github.com/i3/i3/issues/5152 (phpstorm, breakage)
related to https://github.com/i3/i3/issues/5156 (emacsclient, breakage)
2022-09-28 18:29:26 +02:00
Orestis Floros
8ec41334ec Fix motif logic for new floats
- manage.c still used wrong `motif_border_style == BS_NORMAL`
- container must be set to floating first for correct code path and
  correct max_user_border_style to be used in con_set_border_style
- Motif test now includes default_floating_border
2022-09-24 20:46:47 +02:00
Orestis Floros
f6097d4a37 Motif hints: Respect maximum border style configuration set by user
Context:
Motif hints [1] allow applications to request specific window manager
frame decorations. Most applications like alacritty, chromium, and
godot, use the hints as a binary flag, setting or un-setting
`MWM_DECOR_ALL`.
Previously [2], we had disallowed applications to set the "normal"
border style through motif hints. This effectively meant that users that
had set `default_border pixel` would not see applications spawning with
normal decorations [3].
However, that meant that applications like godot [4] could not toggle
their border between none and normal so the behaviour changed with
v4.21 [5].
That change however also allowed applications to override the default
none/pixel border style the user set. For example, alacritty can be
configured to either have all or no decorations [6] and they always set
the motif hint on startup, completely overriding i3 user's preference:
1. If decorations are disabled with alacritty's config then they will
   override `default_border normal` and no title will be used.
2. If decorations are enabled (also the default behavior) with
   alacritty's config then they will override `default_border pixel` and
   a title will be used.

This patch redefines how we interpret motif hints. When a client sets
`MWM_DECOR_ALL`, we interpret it as "the maximum decoration the user has
allowed for this window". I.e., if a client was all decorations and the
user expects the window to not have a title, we don't include the title
in "all" decorations.

The user's preference is determined by these:
1. For new tiling windows, as set by `default_border`
2. For new floating windows, as set by `default_floating_border`
3. For all windows that the user runs the `border` command, whatever is
   the result of that command for that window.
Example:
- User opens new tiling window with `default_border pixel` => maximum
  decoration = PIXEL
- Window requests all/title decorations => i3 enforces the user maximum
  decoration, PIXEL (no change)
- Window requests no decorations => i3 accepts it and sets border to
  NONE, maximum decoration remains PIXEL
- User toggles the border, next style is NORMAL => maximum decoration is
  now NORMAL
- Window requests no decorations => i3 accepts it and sets border to
  NONE
- Window requests all/title decorations => i3 accepts it and sets the
  maximum border, NORMAL
- User toggles the border, next style is NONE => maximum decoration is
  now NONE
- Window requests all/title decorations => i3 enforces the user maximum
  decoration, NONE (no change)

With this, we will still allow behaviour where windows can toggle their
border style with motif hints [4][7].

Reference/footnotes:
[1]: https://linux.die.net/man/3/vendorshell
[2]: https://github.com/i3/i3/pull/2386
[3]: Notice how there is apparently a gap because `default border none`
settings would not be respected if an application wanted just "border"
decorations but this was never reported, probably because of the rare
conjunction of applications requesting that and users defaulting to none
borders.
[4]: https://github.com/godotengine/godot/issues/40037
[5]: https://github.com/i3/i3/pull/5135
[6]: Set by an underlying library here:
fafdedfb7d/src/platform_impl/linux/x11/util/hint.rs (L113-L142)
called by alactitty here:
4ddb608563/alacritty/src/display/window.rs (L341)
[7]: https://github.com/i3/i3/issues/3678

Closes #3678
Fixes #5149
2022-09-24 20:46:47 +02:00
Orestis Floros
eddced6b45 Order border_style_t enum according to amount of decoration
The only place where this matters is with command `border toggle` which
cycles through them. Luckily, the behaviour does not change because the
order is the same with the new enum.
2022-09-24 20:46:47 +02:00
Orestis Floros
8e9b29419f
Merge pull request #5151 from orestisfl/release-notes
Clean up 4.21 release notes
2022-09-22 15:37:03 +02:00
Orestis Floros
016d0b5f07
Clean up 4.21 release notes 2022-09-22 15:28:53 +02:00
Michael Stapelberg
4ab34d5042
GitHub Actions: skip build + test steps in meson dist (#5148)
Originally I thought we should remove the build and test steps in favor of the
“meson dist” step. But, then we lose verbose build command lines and access
to the test logs, and having the failing test logs readily available is
critical.

So, instead, let’s make the “meson dist” step not repeat the work that was
already done in earlier steps. The Meson manual even documents the --no-tests
flag precisely for our use case:

> The meson dist command has a --no-tests option to skip build and tests steps
> of generated packages. It can be used to not waste time for example when done
> in CI that already does its own testing.

From https://mesonbuild.com/Creating-releases.html

fixes https://github.com/i3/i3/issues/5145
2022-09-21 22:19:57 +02:00
Michael Stapelberg
28671a622b release.sh: re-add warning about debian/changelog changes
As long as we push auto builder packages from our CI, we need to update the
version number via the changelog.
2022-09-21 21:58:41 +02:00
Michael Stapelberg
3e434ba0ce release.sh: latest released version numbers 2022-09-21 21:58:41 +02:00
Michael Stapelberg
6ae232a323 release.sh: fix Debian source repository configuration
Debian switched to deb822 sources.list:

https://twitter.com/zekjur/status/1572622368492888065
2022-09-21 21:58:41 +02:00
Michael Stapelberg
5caf49323c release.sh: fix regexp for updating version in index.html 2022-09-21 21:58:41 +02:00
Michael Stapelberg
12cdf435aa release.sh: update website branch name 2022-09-21 21:58:41 +02:00
Michael Stapelberg
d7b9a45ff3 release.sh: remove dput instruction
Package maintenance is done by sur5r these days (thanks!)
2022-09-21 21:58:41 +02:00
Erich Heine
0967021858 Adds sticky field to get_tree reply in ipc doc 2022-09-21 19:27:32 +02:00
Michael Stapelberg
f0856c285c debian: update changelog 2022-09-21 18:38:54 +02:00
Michael Stapelberg
ac95dffd6b Update debian/changelog 2022-09-21 18:26:55 +02:00
Michael Stapelberg
2bdcae8149 Merge branch 'release-4.21' 2022-09-21 18:26:55 +02:00
Michael Stapelberg
c0ef3caec8 Merge branch 'next' into stable 2022-09-21 18:26:55 +02:00
Michael Stapelberg
d7f4707e05 Restore non-git version suffix 2022-09-21 18:26:55 +02:00
Michael Stapelberg
5bc4280a48 release i3 4.21 2022-09-21 18:26:43 +02:00
Michael Stapelberg
8ade46bdf0
unflake t/289-ipc-shutdown-event.t (#5144)
Before this commit, the test was flaky: it relied on the Perl test process
sending the kill() system call before i3 exited. This can easily be triggered
by adding a sleep(1) after the “cmd 'exit'” line.

This is because with i3_autostart => 1 (the default), i3test.pm kills i3
or bails out if it can’t.

So, we instead set i3_autostart => 0 and launch i3 ourselves.
This will unfortunately still make the code kill i3 and bail out,
because launch_with_config updates the $i3_pid variable that i3test.pm
uses for tracking whether it should clean up i3.
The solution is to exit i3 by calling exit_gracefully,
which will make the i3test.pm state correct.

related to https://github.com/i3/i3/issues/3009
2022-09-21 17:47:40 +02:00
Orestis Floros
227c1538be
Add some release notes (#5138) 2022-09-20 09:13:34 +02:00
Orestis Floros
516d442e9a
tiling_drag: Correctly switch to workspace when dragging across outputs (#5141)
Fixes #5089
2022-09-20 09:13:14 +02:00
viri
8252144cc3
motif: restore BS_NORMAL correctly (#5135)
motif hints: restore BS_NORMAL correctly
2022-09-19 20:12:15 +02:00
Orestis Floros
0ac5e248f2
tiling_drag: con_rect_plus_deco_height: Fix underflow (#5129)
Fixes #5069
2022-09-17 13:00:29 +02:00
zhrvn
5ce8e3241b
Fix crash in parse_file() when parsing nested variables (#5003)
Count extra_bytes correctly

If there is a variable with the same name as the rest of another
variable after removing $, then it will be counted twice. Therefore,
we need to completely replace it with spaces (variable names cannot
contain spaces) in order to correctly calculate the length of a new
string.

fixes https://github.com/i3/i3/pull/5002

Co-authored-by: Ivan Zharov <zhiv.email@gmail.com>
Co-authored-by: Michael Stapelberg <stapelberg@users.noreply.github.com>
2022-09-12 09:03:50 +02:00
zhrvn
e48b9aa284
Fix segfault with mode "default" key bindings (#5007)
ignore bindings when not in a valid mode

Co-authored-by: Ivan Zharov <zhiv.email@gmail.com>
Co-authored-by: Michael Stapelberg <stapelberg@users.noreply.github.com>
2022-09-11 15:22:01 +02:00
Michael Stapelberg
f795c2c8da
testsuite: catch i3 SIGSEGV by installing SIGCHLD handler (#5123)
The testsuite already contains quite a number of SIGCHLD handler
installation/un-installations. Here is my attempt at an inventary.

1. complete-run.pl installs a SIGCHLD handler in the `start_xserver()` function
   call, which prints an error and exits when all x server processes have exited.

2. In the TestWorker child process, a SIGCHLD handler is installed to reap dead
   test child processes.

3. The TestWorker child process forks another child process for running the test
   file, where the previously installed SIGCHLD handler (point 2) is unset.

   This is where this commit comes in: it installs a SIGCHLD handler in the test
   file child process, which will trigger when the i3 subprocess dies.

4. (For completeness: i3test.pm defines an END block where it unsets the
    previous SIGCHLD handler before it kills the subprocesses.)

With this commit, when i3 segfaults, the output will look like this:

    Writing logfile to 'testsuite-2022-09-10-21-14-46-4.20-103-gb242bceb/complete-run.log'...
    [:100] /home/michael/i3/testcases/t/167-workspace_layout.t: BAILOUT
    completed 0 of 1 tests
    test /home/michael/i3/testcases/t/167-workspace_layout.t bailed out:
    could not kill i3: No such process

fixes https://github.com/i3/i3/issues/4437
2022-09-10 21:29:04 +02:00
Michael Stapelberg
4b5ead023e
open_logbuffer: avoid overflow by comparing (long long) (#5113)
I tested this on a machine with 256 GB of RAM.

fixes #4906
2022-09-09 10:26:17 +02:00
Michael Stapelberg
ac368e7916
config_parser: prevent trailing whitespace in output (string → word) (#5117)
fixes https://github.com/i3/i3/issues/5064
2022-09-09 10:23:55 +02:00
Michael Stapelberg
6fb58eb841
config: set bar block font to i3-wide font *after* parsing (#5118)
Otherwise, the font directive needs to come before bar blocks,
which is surprising to users.

fixes https://github.com/i3/i3/issues/5031
2022-09-09 10:21:33 +02:00
paperluigis
4db383e430
man/i3-input: fix a typo: chose → choose (#5087) 2022-09-06 20:30:23 +02:00
mariano
e9c63d3001
Add wezterm to i3-sensible-terminal (#5107) 2022-09-06 20:23:56 +02:00
bodea
b242bcebcf
i3-sensible-pager: sanitize LESS environment variable (#5111)
When an error is encountered such as "The configured command for this shortcut
could not be run successfully", the "show errors" button on i3-nagbar doesn't
work if $PAGER is less, and $LESS contains either the -E or -F flag (the
window pops up, but immediately disappears).

Strip these flags from the LESS environment variable before invoking $pager.
2022-09-06 08:42:02 +02:00
116 changed files with 4242 additions and 802 deletions

View File

@ -23,31 +23,22 @@ jobs:
BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: git fetch --prune --unshallow
- name: construct container name
run: |
echo "BASENAME=i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)" >> $GITHUB_ENV
echo "BASENAME_386=i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)" >> $GITHUB_ENV
echo "BASENAME_UBUNTU=i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)" >> $GITHUB_ENV
echo "BASENAME_UBUNTU_386=i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)" >> $GITHUB_ENV
- name: fetch or build Docker container
run: |
docker pull ${{ env.BASENAME }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME }} travis/travis-base.Dockerfile
- name: fetch or build extra Docker containers
if: github.ref == 'refs/heads/next'
run: |
echo "::group::Ubuntu amd64"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
echo "::endgroup::"
echo "::group::Debian i386"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_386 }} travis/travis-base-386.Dockerfile
echo "::endgroup::"
echo "::group::Ubuntu i386"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU_386 }} travis/travis-base-ubuntu-386.Dockerfile
echo "::endgroup::"
docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
- name: build i3
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common -D_FORTIFY_SOURCE=3" meson setup .. -Ddocs=true -Dmans=true -Db_sanitize=address --buildtype=debugoptimized && ninja -v'
- name: check spelling
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl
@ -62,41 +53,39 @@ jobs:
if: ${{ failure() }}
- name: build dist tarball
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson .. -Ddocs=true -Dmans=true && ninja -v dist'
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson setup .. -Ddocs=true -Dmans=true && meson dist --no-tests'
- name: build Debian packages
if: github.ref == 'refs/heads/next'
run: |
echo "::group::Debian amd64"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
echo "::endgroup::"
echo "::group::Ubuntu amd64"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
echo "::endgroup::"
echo "::group::Debian i386"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_386 }} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
echo "::endgroup::"
echo "::group::Ubuntu i386"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU_386 }} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
echo "::endgroup::"
- name: push Debian packages to balto
if: github.ref == 'refs/heads/next'
run: |
./travis/skip-pkg.sh || travis/push-balto.sh
travis/push-balto.sh
- name: build docs
if: github.ref == 'refs/heads/next'
run: |
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
- name: push docs to GitHub pages
if: github.ref == 'refs/heads/next'
run: |
./travis/skip-pkg.sh || travis/deploy-github-pages.sh
travis/deploy-github-pages.sh
formatting:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: check & print release notes
run: ./release-notes/generator.pl
- name: Install dependencies
run: |
sudo apt-get install -y clang-format-10
sudo apt-get install -y clang-format-12
- name: Check formatting
run: clang-format-10 --dry-run --Werror $(git ls-files '*.c' '*.h')
run: clang-format-12 --dry-run --Werror $(git ls-files '*.c' '*.h')
- name: Verify safe wrapper functions are used
run: ./travis/check-safe-wrappers.sh

View File

@ -1,27 +0,0 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.20.1 │
└──────────────────────────────┘
This is i3 v4.20.1. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• i3bar: fix crash with multiple monitors
• xmlto: fix broken .TH line by extending title length
• i3-msg: fix --raw short form (-r) in manpage
• libi3: add missing sys/stat.h header
• use getcwd(NULL, 0) instead of GNU extension get_current_dir_name()
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
rvalieris, Jakob Haufe, lycurgus, Baptiste Daroussin
-- Michael Stapelberg, 2021-11-03

55
RELEASE-NOTES-4.22 Normal file
View File

@ -0,0 +1,55 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.22 │
└──────────────────────────────┘
This is i3 v4.22. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
The biggest change in this release is the merge of the i3-gaps fork.
The i3-gaps fork was the most popular fork of i3, adding the option to
show gaps between tiled windows and/or the screen edges.
See https://i3wm.org/docs/userguide.html#gaps for more details.
Instead of maintaining two versions of i3 (both upstream and downstream,
meaning in Linux distributions and other package collections),
we concluded it would be better for everyone to merge this feature.
For users of i3: gaps are off by default, so there is no change in behavior.
For users of i3-gaps: the configuration is compatible, so you can switch
to i3 v4.22 or newer, without any changes in behavior.
Thanks to Ingo Bürk for maintaining i3-gaps for many years,
for becoming a core i3 maintainer and for helping make this merge possible!
┌────────────────────────────┐
│ Changes in i3 v4.22 │
└────────────────────────────┘
• i3bar: bar { padding } config directive now implemented (supports bar { height } from i3-gaps)
• i3-dmenu-desktop: allow more than one --entry-type with the --show-duplicates flag
• You can now enable gaps using the gaps config directive and/or command
• colors now support an optional alpha value at the end (#rrggbbaa)
• the hide_edge_borders option now supports the smart_no_gaps keyword
• Support nonprimary keyword for outputs
• add "mode" field in binding event
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• gaps: workspace gaps assignments are no longer order-dependent
• Fix compliance to _MOTIF_WM_HINTS spec when all decorations are set
• The floating_from and tiling_from criteria now also work in commands
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
bodea, Demian, Erich Heine, Ingo Bürk, Matias Goldfeld, Orestis Floros,
Tudor Brindus
-- Michael Stapelberg, 2023-01-02

24
debian/changelog vendored
View File

@ -1,3 +1,27 @@
i3-wm (4.22-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Mon, 02 Jan 2023 09:46:22 +0100
i3-wm (4.21.2-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Mon, 24 Oct 2022 21:22:36 +0200
i3-wm (4.21.1-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Mon, 24 Oct 2022 21:22:36 +0200
i3-wm (4.21-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Wed, 21 Sep 2022 18:14:14 +0200
i3-wm (4.20.1-1) unstable; urgency=medium
* New upstream release.

BIN
docs/gaps1920.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -564,9 +564,7 @@ split container.
==== Default layout
In default layout, containers are placed horizontally or vertically next to
each other (depending on the +con->orientation+). If a child is a leaf node (as
opposed to a split container) and has border style "normal", appropriate space
will be reserved for its window decoration.
each other (depending on the +con->orientation+).
==== Stacked layout

View File

@ -0,0 +1,184 @@
i3bar workspace buttons protocol
================================
This document explains the protocol in which i3bar expects input for
configuring workspace buttons. This feature is available since i3 version 4.23.
The program defined by the +workspace_command+ configuration option for i3bar can
modify the workspace buttons displayed by i3bar. The command should constantly
print in its standard output a stream of messages following the protocol
defined in this page.
If you are looking for the status line protocol instead, see https://i3wm.org/docs/i3bar-protocol.html.
== The protocol
Each message should be a newline-delimited JSON array. The array is in the same
format as the +GET_WORKSPACES+ ipc event, see
https://i3wm.org/docs/ipc.html#_workspaces_reply.
As an example, this is the output of the +i3-msg -t get_workspaces+ command:
------------------------------
[
{
"id": 94131549984064,
"num": 1,
"name": "1",
"visible": false,
"focused": false,
"output": "HDMI-A-0",
"urgent": false
},
{
"id": 94131550477584,
"num": 2,
"name": "2",
"visible": true,
"focused": true,
"output": "HDMI-A-0",
"urgent": false
},
{
"id": 94131550452704,
"num": 3,
"name": "3:some workspace",
"visible": false,
"focused": false,
"output": "HDMI-A-0",
"urgent": false
}
]
------------------------------
Please note that this example was pretty printed for human consumption, with
the +"rect"+ field removed. Workspace button commands should output each array
in one line.
Each element in the array represents a workspace. i3bar creates one workspace
button for each element in the array. The order of these buttons is the same as
the order of the elements in the array.
In general, we recommend subscribing to the +workspace+ and +output+
https://i3wm.org/docs/ipc.html#_workspace_event[events],
fetching the current workspace information with +GET_WORKSPACES+, modifying the
JSON array in the response according to your needs and then printing it to the
standard output. However, you are free to build a new message from the ground
up.
=== Workspace objects in detail
The documentation of +GET_WORKSPACES+ should be sufficient to understand the
meaning of each property but here we provide extra notes for each property and
its meaning with respect to i3bar.
All properties but +name+ are optional.
id (integer)::
If it is included it will be used to switch to that workspace when you
click the corresponding button. If it's not provided, the +name+ will be
used. You can use the +id+ field to present workspaces under a modified
name.
num (integer)::
The only use of a workspace's number is if the +strip_workspace_numbers+
setting is enabled.
name (string)::
The only required property. If an +id+ is provided you can freely change
the +name+ as you wish, effectively renaming the buttons of i3bar.
visible (boolean)::
Defaults to +false+ if not included. +focused+ takes precedence over it,
however +visible+ is important for more than one monitors.
focused (boolean)::
Defaults to +false+ if not included. Generally, exactly one of the
workspaces should be +focused+. If not, no button will have the
+focused_workspace+ color.
urgent (boolean)::
Defaults to +false+ if not included.
rect (map)::
Not used by i3bar but will be ignored.
output (string)::
Defaults to the primary output if not included.
== Examples
These example scripts require the https://stedolan.github.io/jq/[jq] utility to
be installed but otherwise just use the standard +i3-msg+ utility included with
i3. However, you can write your own scripts in your preferred language, with
the help of one of the
https://i3wm.org/docs/ipc.html#_see_also_existing_libraries[pre-existing i3
libraries]
=== Base configuration
------------------------------
bar {
workspace_command /path/to/your/script.sh
}
------------------------------
=== Re-create the default behaviour of i3bar
Not very useful by itself but this will be the basic building block of all the
following scripts. This one does not require +jq+.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
# Initially print the current workspaces before we receive any events. This
# avoids having an empty bar when starting up.
i3-msg -t get_workspaces;
# Then, while we receive events, update the workspace information.
while read; do i3-msg -t get_workspaces; done;
}
------------------------------
=== Hide workspace named +foo+ unless if it is focused.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '[ .[] | select(.name != "foo" or .focused) ]'
------------------------------
Important! Make sure you use the +--unbuffered+ flag with +jq+, otherwise you
might not get the changes in real-time but whenever they are flushed, which
might mean that you are getting an empty bar until enough events are written.
=== Show empty workspaces +foo+ and +bar+ on LVDS1 even if they do not exist at the moment.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '
def fake_ws(name): {
name: name,
output: "LVDS1",
};
. + [ fake_ws("foo"), fake_ws("bar") ] | unique_by(.name)
'
------------------------------
=== Sort workspaces in reverse alphanumeric order
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c 'sort_by(.name) | reverse'
------------------------------
=== Append "foo" to the name of each workspace
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '[ .[] | .name |= . + " foo" ]'
------------------------------

View File

@ -410,6 +410,14 @@ deco_rect (map)::
The coordinates of the *window decoration* inside its container. These
coordinates are relative to the container and do not include the actual
client window.
actual_deco_rect (map)::
See +deco_rect+. i3 v4.22 changed the way title bars are rendered. Before
i3 v4.22, the deco_rect was always relative to the parent coordinates.
Starting with i3 v4.22, this remains true for tabbed/stacked containers
(actual_deco_rect is identical to deco_rect), but for normal-border leaf
containers within vertical/horizontal split containers, actual_deco_rect
is relative to the container itself. For more background, see
https://github.com/i3/i3/issues/1966
geometry (map)::
The original geometry the window specified when i3 mapped it. Used when
switching a window to floating mode, for example.
@ -440,6 +448,9 @@ focus (array of integer)::
order. Traversing the tree by following the first entry in this array
will result in eventually reaching the one node with +focused+ set to
true.
sticky (bool)::
Whether this window is "sticky". If it is also floating, this window will
be present on all workspaces on the same output.
fullscreen_mode (integer)::
Whether this container is in fullscreen state or not.
Possible values are
@ -1088,6 +1099,8 @@ binding that ran a command because of user input. The +change (string)+ field
indicates what sort of binding event was triggered (right now it will always be
+"run"+ but may be expanded in the future).
The +mode (string)+ field contains the name of the mode the binding was run in.
The +binding (object)+ field contains details about the binding that was run:
command (string)::

View File

@ -196,6 +196,7 @@ provided by the i3 https://github.com/i3/i3/blob/next/etc/config.keycodes[defaul
Floating windows are always on top of tiling windows.
[[tiling_drag]]
=== Moving tiling containers with the mouse
Since i3 4.21, it's possible to drag tiling containers using the mouse. The
@ -316,7 +317,7 @@ single workspace on which you open three terminal windows. All these terminal
windows are directly attached to one node inside i3s layout tree, the
workspace node. By default, the workspace nodes 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 nodes orientation will be changed to +vertical+. The terminal window
you moved down is directly attached to the workspace and appears on the bottom
of the screen. A new (horizontal) container was created to accommodate the
@ -777,14 +778,19 @@ default_border pixel 3
=== Hiding borders adjacent to the screen edges
You can hide container borders adjacent to the screen edges using
+hide_edge_borders+. This is useful if you are using scrollbars, or do not want
to waste even two pixels in displayspace. The "smart" setting hides borders on
workspaces with only one window visible, but keeps them on workspaces with
multiple windows visible. Default is none.
+hide_edge_borders+ (the default is +none+). Hiding borders is useful if you are
using scrollbars, or do not want to waste even two pixels in displayspace.
The "smart" setting hides borders on workspaces with only one window visible,
but keeps them on workspaces with multiple windows visible.
The "smart_no_gaps" setting hides edge-specific borders of a container if the
container is the only container on its workspace and the gaps to the screen edge
are +0+.
*Syntax*:
-----------------------------------------------
hide_edge_borders none|vertical|horizontal|both|smart
hide_edge_borders none|vertical|horizontal|both|smart|smart_no_gaps
-----------------------------------------------
*Example*:
@ -792,6 +798,27 @@ hide_edge_borders none|vertical|horizontal|both|smart
hide_edge_borders vertical
----------------------
[[_smart_borders]]
=== Smart borders
Smart borders will draw borders on windows only if there is more than one window
in a workspace. This feature can also be enabled only if the gap size between
window and screen edge is +0+.
*Syntax*:
-----------------------------------------------
smart_borders on|off|no_gaps
-----------------------------------------------
*Example*:
----------------------
# Activate smart borders (always)
smart_borders on
# Activate smart borders (only when there are effectively no gaps)
smart_borders no_gaps
----------------------
[[for_window]]
=== Arbitrary commands for specific windows (for_window)
@ -938,7 +965,7 @@ considered.
*Syntax*:
------------------------------------------------------------
assign <criteria> [→] [workspace] [number] <workspace>
assign <criteria> [→] output left|right|up|down|primary|<output>
assign <criteria> [→] output left|right|up|down|primary|nonprimary|<output>
------------------------------------------------------------
*Examples*:
@ -970,6 +997,9 @@ assign [class="^URxvt$"] → output right
# Assign urxvt to the primary output
assign [class="^URxvt$"] → output primary
# Assign urxvt to the first non-primary output
assign [class="^URxvt$"] → output nonprimary
----------------------
Note that you might not have a primary output configured yet. To do so, run:
@ -1117,7 +1147,8 @@ client.background::
which do not cover the whole area of this window expose the color. Note
that this colorclass only takes a single color.
Colors are in HTML hex format (#rrggbb), see the following example:
Colors are in HTML hex format (#rrggbb, optionally #rrggbbaa), see the following
example:
*Examples (default colors)*:
----------------------------------------------------------------------
@ -1402,6 +1433,103 @@ fullscreen toggle
bindsym Mod1+F fullscreen toggle
-------------------
[[config_tiling_drag]]
=== Tiling drag
You can configure how to initiate the tiling drag feature (see <<tiling_drag>>).
*Syntax*:
--------------------------------
tiling_drag off
tiling_drag modifier|titlebar [modifier|titlebar]
--------------------------------
*Examples*:
--------------------------------
# Only initiate a tiling drag when the modifier is held:
tiling_drag modifier
# Initiate a tiling drag on either titlebar click or held modifier:
tiling_drag modifier titlebar
# Disable tiling drag altogether
tiling_drag off
--------------------------------
[[gaps]]
=== Gaps
Since i3 4.22, you can configure window gaps.
“Gaps” are added spacing between windows (or split containers) and to the screen edges:
image::gaps1920.png["Screenshot of i3 with gaps enabled (10 px inner gaps, 20 px outer gaps)",title="Gaps enabled (10 px inner gaps, 20 px outer gaps)"]
You can configure two different kind of gaps:
1. Inner gaps are space between two adjacent windows (or split containers).
2. Outer gaps are space along the screen edges. You can configure each side
(left, right, top, bottom) separately.
If you are familiar with HTML and CSS, you can think of inner gaps as `padding`,
and of outer gaps as `margin`, applied to a `<div>` around your window or split
container.
Note that outer gaps are added to the inner gaps, meaning the total gap size
between a screen edge and a window (or split container) will be the sum of outer
and inner gaps.
You can define gaps either globally or per workspace using the following
syntax.
*Syntax*:
-----------------------
# Inner gaps for all windows: space between two adjacent windows (or split containers).
gaps inner <gap_size>[px]
# Outer gaps for all windows: space along the screen edges.
gaps outer|horizontal|vertical|top|left|bottom|right <gap_size>[px]
# Inner and outer gaps for all windows on a specific workspace.
# <ws> can be a workspace number or name.
workspace <ws> gaps inner <gap_size>[px]
workspace <ws> gaps outer|horizontal|vertical|top|left|bottom|right <gap_size>[px]
# Enabling “Smart Gaps” means no gaps will be shown when there is
# precisely one window or split container on the workspace.
#
# inverse_outer only enables outer gaps when there is exactly one
# window or split container on the workspace.
smart_gaps on|off|inverse_outer
-----------------------
Outer gaps can be configured for each side individually with the `top`, `left`,
`bottom` and `right` directive. `horizontal` and `vertical` are shortcuts for
`left`/`right` and `top`/`bottom`, respectively.
*Example*:
-------------------------------------------------
# Configure 5px of space between windows and to the screen edges.
gaps inner 5px
# Configure an additional 5px of extra space to the screen edges,
# for a total gap of 10px to the screen edges, and 5px between windows.
gaps outer 5px
# Overwrite gaps to 0, I need all the space I can get on workspace 3.
workspace 3 gaps inner 0
workspace 3 gaps outer 0
# Only enable outer gaps when there is exactly one window or split container on the workspace.
smart_gaps inverse_outer
-------------------------------------------------
Tip: Gaps can additionally be changed at runtime with the `gaps` command, see
<<changing_gaps>>.
Tip: You can find an
https://github.com/Airblader/i3/wiki/Example-Configuration[example
configuration] that uses modes to illustrate various gap configurations.
== Configuring i3bar
The bar at the bottom of your monitor is drawn by a separate process called
@ -1483,6 +1611,30 @@ bar {
}
-------------------------------------------------
[[workspace_command]]
=== Workspace buttons command
Since i3 4.23, i3bar can run a program and use its +stdout+ output to define
the workspace buttons displayed on the left hand side of the bar. With this
feature, you can, for example, rename the buttons of workspaces, hide specific
workspaces, always show a workspace button even if the workspace does not exist
or change the order of the buttons.
Also see <<status_command>> for the statusline option and
https://i3wm.org/docs/i3bar-workspace-protocol.html for the detailed protocol.
*Syntax*:
------------------------
workspace_command <command>
------------------------
*Example*:
-------------------------------------------------
bar {
workspace_command /path/to/script.sh
}
-------------------------------------------------
=== Display mode
You can either have i3bar be visible permanently at one edge of the screen
@ -1521,7 +1673,7 @@ the windows key). The default value for the hidden_state is hide.
mode dock|hide|invisible
hidden_state hide|show
modifier <Modifier>|none
------------------------
-------------------------
*Example*:
----------------
@ -1691,7 +1843,7 @@ tray_output none|primary|<output>
---------------------------------
*Example*:
-------------------------
----------------------------------
# disable system tray
bar {
tray_output none
@ -1706,7 +1858,7 @@ bar {
bar {
tray_output HDMI2
}
-------------------------
----------------------------------
Note that you might not have a primary output configured yet. To do so, run:
-------------------------
@ -1730,10 +1882,10 @@ tray_padding <px> [px]
-------------------------
*Example*:
-------------------------
---------------------------
# Obey Fitts's law
tray_padding 0
-------------------------
---------------------------
=== Font
@ -1871,8 +2023,8 @@ bar {
=== Colors
As with i3, colors are in HTML hex format (#rrggbb). The following colors can
be configured at the moment:
As with i3, colors are in HTML hex format (#rrggbb, optionally #rrggbbaa). The
following colors can be configured at the moment:
background::
Background color of the bar.
@ -1956,6 +2108,49 @@ while +#000000FF+ will be a fully opaque black (the same as +#000000+).
Please note that due to the way the tray specification works, enabling this
flag will cause all tray icons to have a transparent background.
[[i3bar_padding]]
=== Padding
To make i3bar higher (without increasing the font size), and/or add padding to
the left and right side of i3bar, you can use the +padding+ directive:
*Syntax*:
--------------------------------------
bar {
# 2px left/right and 2px top/bottom padding
padding 2px
# 2px top/bottom padding, no left/right padding
padding 2px 0
# 2px top padding, no left/right padding, 4px bottom padding
padding 2px 0 4px
# four value syntax
padding top[px] right[px] bottom[px] left[px]
}
--------------------------------------
*Examples*:
--------------------------------------
bar {
# 2px left/right and 2px top/bottom padding
padding 2px
# 2px top/bottom padding, no left/right padding
padding 2px 0
# 2px top padding, no left/right padding, 4px bottom padding
padding 2px 0 4px
# 2px top padding, 6px right padding, 4px bottom padding, 1px left padding
padding 2px 6px 4px 1px
}
--------------------------------------
Note: As a convenience for users who migrate from i3-gaps to i3, the +height+
directive from i3-gaps is supported by i3, but should be changed to +padding+.
[[list_of_commands]]
== List of commands
@ -2226,6 +2421,9 @@ available:
<criteria>::
Sets focus to the container that matches the specified criteria.
See <<command_criteria>>.
workspace::
Sets focus to the workspace that contains the container that matches the
specified criteria.
left|right|up|down::
Sets focus to the nearest container in the given direction.
parent::
@ -2252,10 +2450,11 @@ output::
*Syntax*:
----------------------------------------------
<criteria> focus
<criteria> focus workspace
focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle
focus next|prev [sibling]
focus output left|right|down|up|current|primary|next|<output1> [output2]…
focus output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
----------------------------------------------
*Examples*:
@ -2263,6 +2462,10 @@ focus output left|right|down|up|current|primary|next|<output1> [output2]…
# Focus firefox
bindsym $mod+F1 [class="Firefox"] focus
# Focus the workspace where firefox is, without necessarily focusing firefox
# itself.
bindsym $mod+x [class="Firefox"] focus workspace
# Focus container on the left, bottom, top, right
bindsym $mod+j focus left
bindsym $mod+k focus down
@ -2287,8 +2490,11 @@ bindsym $mod+x focus output HDMI-2
# Focus the primary output
bindsym $mod+x focus output primary
# Cycle focus through non-primary outputs
bindsym $mod+x focus output nonprimary
# Cycle focus between outputs VGA1 and LVDS1 but not DVI0
bindsym $mod+x move workspace to output VGA1 LVDS1
bindsym $mod+x focus output VGA1 LVDS1
-------------------------------------------------
Note that you might not have a primary output configured yet. To do so, run:
@ -2550,9 +2756,9 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
+right+, +up+ or +down+), there are two commands:
*Syntax*:
------------------------------------------------------------
move container to output left|right|down|up|current|primary|next|<output1> [output2]…
move workspace to output left|right|down|up|current|primary|next|<output1> [output2]…
-------------------------------------------------------------------------------------
move container to output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
move workspace to output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
-------------------------------------------------------------------------------------
*Examples*:
@ -2774,10 +2980,10 @@ Starting with i3 v4.20, you can optionally enable window icons either for
specific windows or for all windows (using the <<for_window>> directive).
*Syntax*:
-----------------------------
----------------------------------------
title_window_icon <yes|no|toggle>
title_window_icon <padding|toggle> <px>
------------------------------
----------------------------------------
*Examples*:
-------------------------------------------------------------------------------------
@ -2978,6 +3184,35 @@ bindsym $mod+b bar mode hide bar-1
bindsym $mod+Shift+b bar mode invisible bar-1
------------------------------------------------
[[changing_gaps]]
=== Changing gaps
Gaps can be modified at runtime with the following command syntax:
*Syntax*:
-----------------------------------------------
# Inner gaps: space between two adjacent windows (or split containers).
gaps inner current|all set|plus|minus|toggle <gap_size_in_px>
# Outer gaps: space along the screen edges.
gaps outer|horizontal|vertical|top|right|bottom|left current|all set|plus|minus|toggle <gap_size_in_px>
-----------------------------------------------
With `current` or `all` you can change gaps either for only the currently
focused or all currently existing workspaces (note that this does not affect the
global configuration itself).
*Examples*:
----------------------------------------------
gaps inner all set 20
gaps outer current plus 5
gaps horizontal current plus 40
gaps outer current toggle 60
for_window [class="vlc"] gaps inner 0, gaps outer 0
----------------------------------------------
To change the gaps for all windows, see the <<gaps>> configuration directive.
[[multi_monitor]]
== Multiple monitors
@ -3011,7 +3246,8 @@ To help you get going if you have never used multiple monitors before, here is
a short overview of the xrandr options which will probably be of interest to
you. It is always useful to get an overview of the current screen configuration.
Just run "xrandr" and you will get an output like the following:
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
$ xrandr
Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192
VGA1 disconnected (normal left inverted right x axis y axis)
@ -3024,7 +3260,7 @@ LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x
720x400 85.0
640x400 85.1
640x350 85.1
--------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
Several things are important here: You can see that +LVDS1+ is connected (of
course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor
@ -3036,12 +3272,15 @@ combined resolution of your monitors. By default, it is usually too low and has
to be increased by editing +/etc/X11/xorg.conf+.
So, say you connected VGA1 and want to use it as an additional screen:
-------------------------------------------
xrandr --output VGA1 --auto --left-of LVDS1
-------------------------------------------
This command makes xrandr try to find the native resolution of the device
connected to +VGA1+ and configures it to the left of your internal flat panel.
When running "xrandr" again, the output looks like this:
-------------------------------------------------------------------------------
$ xrandr
Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192
@ -3064,6 +3303,7 @@ LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm
640x400 85.1
640x350 85.1
-------------------------------------------------------------------------------
Please note that i3 uses exactly the same API as xrandr does, so it will see
only what you can see in xrandr.

View File

@ -49,6 +49,10 @@ set $right semicolon
# use Mouse+Mod1 to drag floating windows to their wanted position
floating_modifier Mod1
# move tiling windows via drag & drop by left-clicking into the title bar,
# or left-clicking anywhere into the window while holding the floating modifier.
tiling_drag modifier titlebar
# start a terminal
bindsym Mod1+Return exec i3-sensible-terminal

View File

@ -43,6 +43,10 @@ bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOU
# Use Mouse+$mod to drag floating windows to their wanted position
floating_modifier $mod
# move tiling windows via drag & drop by left-clicking into the title bar,
# or left-clicking anywhere into the window while holding the floating modifier.
tiling_drag modifier titlebar
# start a terminal
bindcode $mod+36 exec i3-sensible-terminal

View File

@ -46,9 +46,11 @@ sub slurp {
my @entry_types;
my $dmenu_cmd = 'dmenu -i';
my $show_duplicates;
my $result = GetOptions(
'dmenu=s' => \$dmenu_cmd,
'entry-type=s' => \@entry_types,
'show-duplicates' => \$show_duplicates,
'version' => sub {
say "dmenu-desktop 1.5 © 2012 Michael Stapelberg";
exit 0;
@ -292,7 +294,7 @@ for my $app (keys %apps) {
}
$choices{$name} = $app;
next;
next unless $show_duplicates;
}
if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
@ -327,7 +329,7 @@ for my $app (keys %apps) {
if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) {
$choices{basename($command)} = $app;
}
next;
next unless $show_duplicates;
}
if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
@ -366,9 +368,14 @@ binmode $dmenu_in, ':utf8';
binmode $dmenu_out, ':utf8';
# Feed dmenu the possible choices.
# Since the process might have already exited, we ignore SIGPIPE.
$SIG{PIPE} = 'IGNORE';
say $dmenu_in $_ for sort keys %choices;
close($dmenu_in);
$SIG{PIPE} = 'DEFAULT';
waitpid($pid, 0);
my $status = ($? >> 8);
@ -413,7 +420,7 @@ my $exec = $app->{Exec};
my $location = $app->{_Location};
# Quote as described by “The Exec key”:
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html
sub quote {
my ($str) = @_;
$str =~ s/("|`|\$|\\)/\\$1/g;
@ -425,6 +432,17 @@ $choice = quote($choice);
$location = quote($location);
$name = quote($name);
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html:
#
# Note that the general escape rule for values of type string states that the
# backslash character can be escaped as ("\\") as well and that this escape rule
# is applied before the quoting rule. As such, to unambiguously represent a
# literal backslash character in a quoted argument in a desktop entry file
# requires the use of four successive backslash characters ("\\\\"). Likewise, a
# literal dollar sign in a quoted argument in a desktop entry file is
# unambiguously represented with ("\\$").
$exec =~ s/\\\\/\\/g;
# Remove deprecated field codes, as the spec dictates.
$exec =~ s/%[dDnNvm]//g;
@ -481,9 +499,10 @@ EOT
# starts with a double quote ("), everything is parsed as-is until the next
# double quote which is NOT preceded by a backslash (\).
#
# Therefore, we escape all double quotes (") by replacing them with \"
$exec =~ s/\\"/\\\\\\"/g;
$exec =~ s/([^\\])"/$1\\"/g;
# Therefore, we escape all double quotes (") by replacing them with \".
# To not change the meaning of any double quote, backslashes need to be
# escaped as well.
$exec =~ s/(["\\])/\\$1/g;
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
$nosn = '--no-startup-id';
@ -501,7 +520,7 @@ system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?";
=head1 SYNOPSIS
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name]
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name] [--show-duplicates]
=head1 DESCRIPTION
@ -552,11 +571,15 @@ version of dmenu.
Display the (localized) "Name" (type = name), the command (type = command) or
the (*.desktop) filename (type = filename) in dmenu. This option can be
specified multiple times.
specified multiple times. Duplicates will be removed, see '--show-duplicates'.
Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
command), and "libreoffice-writer" (type = filename).
=item B<--show-duplicates>
Show duplicates if '--entry-type' is specified multiple times.
=back
=head1 VERSION

View File

@ -8,6 +8,11 @@
# Distributions/packagers can enhance this script with a
# distribution-specific mechanism to find the preferred pager.
# The less -E and -F options exit immediately for short files, strip if present.
case "$LESS" in
*[EF]*) LESS=`echo "$LESS" | tr -d EF`
esac
# Hopefully one of these is installed (no flamewars about preference please!):
# We don't use 'more' because it will exit if the file is too short.
# Worst case scenario we'll open the file in your editor.

View File

@ -14,7 +14,7 @@
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
# 3. The terminal emulator with best accessibility comes first.
# 4. No order is guaranteed/desired for the remaining terminal emulators.
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi

View File

@ -11,7 +11,7 @@
#include <config.h>
#include <stdbool.h>
#include <ev.h>
#define STDIN_CHUNK_SIZE 1024
@ -40,6 +40,18 @@ typedef struct {
*/
bool click_events;
bool click_events_init;
/**
* stdin- and SIGCHLD-watchers
*/
ev_io *stdin_io;
ev_child *child_sig;
int stdin_fd;
/**
* Line read from child that did not include a newline character.
*/
char *pending_line;
} i3bar_child;
/*
@ -50,36 +62,66 @@ void clear_statusline(struct statusline_head *head, bool free_resources);
/*
* Start a child process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care
* about arguments and such
* We actually start a shell to execute the command so we don't have to care
* about arguments and such.
*
* If `command' is NULL, such as in the case when no `status_command' is given
* in the bar config, no child will be started.
*
*/
void start_child(char *command);
/*
* Same as start_child but starts the configured client that manages workspace
* buttons.
*
*/
void start_ws_child(char *command);
/*
* Returns true if the status child process is alive.
*
*/
bool status_child_is_alive(void);
/*
* Returns true if the workspace child process is alive.
*
*/
bool ws_child_is_alive(void);
/*
* kill()s the child process (if any). Called when exit()ing.
*
*/
void kill_child_at_exit(void);
void kill_children_at_exit(void);
/*
* kill()s the child process (if any) and closes and
* free()s the stdin- and SIGCHLD-watchers
* kill()s the child process (if any) and closes and free()s the stdin- and
* SIGCHLD-watchers
*
*/
void kill_child(void);
/*
* kill()s the workspace child process (if any) and closes and free()s the
* stdin- and SIGCHLD-watchers.
* Similar to kill_child.
*
*/
void kill_ws_child(void);
/*
* Sends a SIGSTOP to the child process (if existent)
*
*/
void stop_child(void);
void stop_children(void);
/*
* Sends a SIGCONT to the child process (if existent)
*
*/
void cont_child(void);
void cont_children(void);
/*
* Whether or not the child want click events
@ -92,3 +134,14 @@ bool child_want_click_events(void);
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods);
/*
* When workspace_command is enabled this function is used to re-parse the
* latest received JSON from the client.
*/
void repeat_last_ws_json(void);
/*
* Replaces the workspace buttons with an error message.
*/
void set_workspace_button_error(const char *message);

View File

@ -38,11 +38,21 @@ typedef struct tray_output_t {
TAILQ_ENTRY(tray_output_t) tray_outputs;
} tray_output_t;
/* Matches i3/include/data.h */
struct Rect {
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
};
typedef struct config_t {
uint32_t modifier;
TAILQ_HEAD(bindings_head, binding_t) bindings;
position_t position;
bool verbose;
uint32_t bar_height;
struct Rect padding;
bool transparency;
struct xcb_color_strings_t colors;
bool disable_binding_mode_indicator;
@ -52,6 +62,7 @@ typedef struct config_t {
bool strip_ws_name;
char *bar_id;
char *command;
char *workspace_command;
char *fontname;
i3String *separator_symbol;
TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
@ -69,17 +80,17 @@ typedef struct config_t {
extern config_t config;
/**
* Start parsing the received bar configuration JSON string
* Parse the received bar configuration JSON string
*
*/
void parse_config_json(char *json);
void parse_config_json(const unsigned char *json, size_t size);
/**
* Start parsing the received bar configuration list. The only usecase right
* now is to automatically get the first bar id.
* Parse the received bar configuration list. The only usecase right now is to
* automatically get the first bar id.
*
*/
void parse_get_first_i3bar_config(char *json);
void parse_get_first_i3bar_config(const unsigned char *json, size_t size);
/**
* free()s the color strings as soon as they are not needed anymore.

View File

@ -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);

View File

@ -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

View File

@ -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 */

View File

@ -10,6 +10,7 @@
#include "common.h"
#include "yajl_utils.h"
#include <ctype.h> /* isspace */
#include <err.h>
#include <errno.h>
#include <ev.h>
@ -27,14 +28,30 @@
#include <yajl/yajl_parse.h>
/* Global variables for child_*() */
i3bar_child child = {0};
#define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
i3bar_child status_child = {0};
i3bar_child ws_child = {0};
/* stdin- and SIGCHLD-watchers */
ev_io *stdin_io;
int stdin_fd;
ev_child *child_sig;
#define DLOG_CHILD(c) \
do { \
if ((c).pid == 0) { \
DLOG("%s: child pid = 0\n", __func__); \
} else if ((c).pid == status_child.pid) { \
DLOG("%s: status_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
} else if ((c).pid == ws_child.pid) { \
DLOG("%s: workspace_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
} else { \
ELOG("%s: unknown child, this should never happen " \
"pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
} \
} while (0)
#define DLOG_CHILDREN \
do { \
DLOG_CHILD(status_child); \
DLOG_CHILD(ws_child); \
} while (0)
/* JSON parser for stdin */
yajl_handle parser;
@ -127,7 +144,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
finish:
FREE(message);
free(message);
va_end(args);
}
@ -135,22 +152,27 @@ finish:
* Stop and free() the stdin- and SIGCHLD-watchers
*
*/
static void cleanup(void) {
if (stdin_io != NULL) {
ev_io_stop(main_loop, stdin_io);
FREE(stdin_io);
close(stdin_fd);
stdin_fd = 0;
close(child_stdin);
child_stdin = 0;
static void cleanup(i3bar_child *c) {
DLOG_CHILD(*c);
if (c->stdin_io != NULL) {
ev_io_stop(main_loop, c->stdin_io);
FREE(c->stdin_io);
if (c->pid == status_child.pid) {
close(child_stdin);
child_stdin = 0;
}
close(c->stdin_fd);
}
if (child_sig != NULL) {
ev_child_stop(main_loop, child_sig);
FREE(child_sig);
if (c->child_sig != NULL) {
ev_child_stop(main_loop, c->child_sig);
FREE(c->child_sig);
}
memset(&child, 0, sizeof(i3bar_child));
FREE(c->pending_line);
memset(c, 0, sizeof(i3bar_child));
}
/*
@ -362,15 +384,13 @@ static int stdin_end_array(void *context) {
* Returns NULL on EOF.
*
*/
static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
int fd = watcher->fd;
int n = 0;
static unsigned char *get_buffer(int fd, int *ret_buffer_len) {
int rec = 0;
int buffer_len = STDIN_CHUNK_SIZE;
unsigned char *buffer = smalloc(buffer_len + 1);
buffer[0] = '\0';
while (1) {
n = read(fd, buffer + rec, buffer_len - rec);
const ssize_t n = read(fd, buffer + rec, buffer_len - rec);
if (n == -1) {
if (errno == EAGAIN) {
/* finish up */
@ -390,10 +410,11 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE;
buffer = srealloc(buffer, buffer_len);
buffer = srealloc(buffer, buffer_len + 1);
}
}
if (*buffer == '\0') {
buffer[rec] = '\0';
if (buffer[0] == '\0') {
FREE(buffer);
rec = -1;
}
@ -443,13 +464,14 @@ static bool read_json_input(unsigned char *input, int length) {
* in statusline
*
*/
static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
static void stdin_io_cb(int fd) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) {
return;
}
bool has_urgent = false;
if (child.version > 0) {
if (status_child.version > 0) {
has_urgent = read_json_input(buffer, rec);
} else {
read_flat_input((char *)buffer, rec);
@ -463,22 +485,23 @@ static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
* whether this is JSON or plain text
*
*/
static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
static void stdin_io_first_line_cb(int fd) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) {
return;
}
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
/* Detect whether this is JSON or plain text. */
unsigned int consumed = 0;
/* At the moment, we dont care for the version. This might change
* in the future, but for now, we just discard it. */
parse_json_header(&child, buffer, rec, &consumed);
if (child.version > 0) {
/* If hide-on-modifier is set, we start of by sending the
* child a SIGSTOP, because the bars aren't mapped at start */
parse_json_header(&status_child, buffer, rec, &consumed);
if (status_child.version > 0) {
/* If hide-on-modifier is set, we start of by sending the status_child
* a SIGSTOP, because the bars aren't mapped at start */
if (config.hide_on_modifier) {
stop_child();
stop_children();
}
draw_bars(read_json_input(buffer + consumed, rec - consumed));
} else {
@ -489,9 +512,133 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
read_flat_input((char *)buffer, rec);
}
free(buffer);
ev_io_stop(main_loop, stdin_io);
ev_io_init(stdin_io, &stdin_io_cb, stdin_fd, EV_READ);
ev_io_start(main_loop, stdin_io);
}
static bool isempty(char *s) {
while (*s != '\0') {
if (!isspace(*s)) {
return false;
}
s++;
}
return true;
}
static char *append_string(const char *previous, const char *str) {
if (previous != NULL) {
char *result;
sasprintf(&result, "%s%s", previous, str);
return result;
}
return sstrdup(str);
}
static char *ws_last_json;
static void ws_stdin_io_cb(int fd) {
int rec;
unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) {
return;
}
gchar **strings = g_strsplit((const char *)buffer, "\n", 0);
for (int idx = 0; strings[idx] != NULL; idx++) {
if (ws_child.pending_line == NULL && isempty(strings[idx])) {
/* In the normal case where the buffer ends with '\n', the last
* string should be empty */
continue;
}
if (strings[idx + 1] == NULL) {
/* This is the last string but it is not empty, meaning that we have
* read data that is incomplete, save it for later. */
char *new = append_string(ws_child.pending_line, strings[idx]);
free(ws_child.pending_line);
ws_child.pending_line = new;
continue;
}
free(ws_last_json);
ws_last_json = append_string(ws_child.pending_line, strings[idx]);
FREE(ws_child.pending_line);
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
}
g_strfreev(strings);
free(buffer);
draw_bars(false);
}
static void common_stdin_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
if (watcher == status_child.stdin_io) {
if (status_child.version == (uint32_t)-1) {
stdin_io_first_line_cb(watcher->fd);
} else {
stdin_io_cb(watcher->fd);
}
} else if (watcher == ws_child.stdin_io) {
ws_stdin_io_cb(watcher->fd);
} else {
ELOG("Got callback for unknown watcher fd=%d\n", watcher->fd);
}
}
/*
* When workspace_command is enabled this function is used to re-parse the
* latest received JSON from the client.
*/
void repeat_last_ws_json(void) {
if (ws_last_json) {
DLOG("Repeating last workspace JSON\n");
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
}
}
/*
* Wrapper around set_workspace_button_error to mimic the call of
* set_statusline_error.
*/
__attribute__((format(printf, 1, 2))) static void set_workspace_button_error_f(const char *format, ...) {
char *message;
va_list args;
va_start(args, format);
if (vasprintf(&message, format, args) == -1) {
goto finish;
}
set_workspace_button_error(message);
finish:
free(message);
va_end(args);
}
/*
* Replaces the workspace buttons with an error message.
*/
void set_workspace_button_error(const char *message) {
free_workspaces();
char *name = NULL;
sasprintf(&name, "Error: %s", message);
i3_output *output;
SLIST_FOREACH (output, outputs, slist) {
i3_ws *fake_ws = scalloc(1, sizeof(i3_ws));
/* Don't set the canonical_name field to make this workspace unfocusable. */
fake_ws->name = i3string_from_utf8(name);
fake_ws->name_width = predict_text_width(fake_ws->name);
fake_ws->num = -1;
fake_ws->urgent = fake_ws->visible = true;
fake_ws->output = output;
TAILQ_INSERT_TAIL(output->workspaces, fake_ws, tailq);
}
free(name);
}
/*
@ -501,27 +648,45 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
*
*/
static void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
int exit_status = WEXITSTATUS(watcher->rstatus);
const int exit_status = WEXITSTATUS(watcher->rstatus);
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
child.pid,
watcher->pid,
exit_status);
void (*error_function_pointer)(const char *, ...) = NULL;
const char *command_type = "";
i3bar_child *c = NULL;
if (watcher->pid == status_child.pid) {
command_type = "status_command";
error_function_pointer = set_statusline_error;
c = &status_child;
} else if (watcher->pid == ws_child.pid) {
command_type = "workspace_command";
error_function_pointer = set_workspace_button_error_f;
c = &ws_child;
} else {
ELOG("Unknown child pid, this should never happen\n");
return;
}
DLOG_CHILD(*c);
/* this error is most likely caused by a user giving a nonexecutable or
* nonexistent file, so we will handle those cases separately. */
if (exit_status == 126)
set_statusline_error("status_command is not executable (exit %d)", exit_status);
else if (exit_status == 127)
set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status);
else
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
if (exit_status == 126) {
error_function_pointer("%s is not executable (exit %d)", command_type, exit_status);
} else if (exit_status == 127) {
error_function_pointer("%s not found or is missing a library dependency (exit %d)", command_type, exit_status);
} else {
error_function_pointer("%s process exited unexpectedly (exit %d)", command_type, exit_status);
}
cleanup();
cleanup(c);
draw_bars(false);
}
static void child_write_output(void) {
if (child.click_events) {
if (status_child.click_events) {
const unsigned char *output;
size_t size;
ssize_t n;
@ -535,7 +700,7 @@ static void child_write_output(void) {
yajl_gen_clear(gen);
if (n == -1) {
child.click_events = false;
status_child.click_events = false;
kill_child();
set_statusline_error("child_write_output failed");
draw_bars(false);
@ -543,6 +708,41 @@ static void child_write_output(void) {
}
}
static pid_t sfork(void) {
const pid_t pid = fork();
if (pid == -1) {
ELOG("Couldn't fork(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return pid;
}
static void spipe(int pipedes[2]) {
if (pipe(pipedes) == -1) {
err(EXIT_FAILURE, "pipe(pipe_in)");
}
}
static void exec_shell(char *command) {
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
}
static void setup_child_cb(i3bar_child *child) {
/* We set O_NONBLOCK because blocking is evil in event-driven software */
fcntl(child->stdin_fd, F_SETFL, O_NONBLOCK);
child->stdin_io = smalloc(sizeof(ev_io));
ev_io_init(child->stdin_io, &common_stdin_cb, child->stdin_fd, EV_READ);
ev_io_start(main_loop, child->stdin_io);
/* We must cleanup, if the child unexpectedly terminates */
child->child_sig = smalloc(sizeof(ev_child));
ev_child_init(child->child_sig, &child_sig_cb, child->pid, 0);
ev_child_start(main_loop, child->child_sig);
DLOG_CHILD(*child);
}
/*
* Start a child process with the specified command and reroute stdin.
* We actually start a shell to execute the command so we don't have to care
@ -553,8 +753,9 @@ static void child_write_output(void) {
*
*/
void start_child(char *command) {
if (command == NULL)
if (command == NULL) {
return;
}
/* Allocate a yajl parser which will be used to parse stdin. */
static yajl_callbacks callbacks = {
@ -568,69 +769,77 @@ void start_child(char *command) {
.yajl_end_array = stdin_end_array,
};
parser = yajl_alloc(&callbacks, NULL, &parser_context);
gen = yajl_gen_alloc(NULL);
int pipe_in[2]; /* pipe we read from */
int pipe_out[2]; /* pipe we write to */
spipe(pipe_in);
spipe(pipe_out);
if (pipe(pipe_in) == -1)
err(EXIT_FAILURE, "pipe(pipe_in)");
if (pipe(pipe_out) == -1)
err(EXIT_FAILURE, "pipe(pipe_out)");
status_child.pid = sfork();
if (status_child.pid == 0) {
/* Child-process. Reroute streams and start shell */
close(pipe_in[0]);
close(pipe_out[1]);
child.pid = fork();
switch (child.pid) {
case -1:
ELOG("Couldn't fork(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
case 0:
/* Child-process. Reroute streams and start shell */
dup2(pipe_in[1], STDOUT_FILENO);
dup2(pipe_out[0], STDIN_FILENO);
close(pipe_in[0]);
close(pipe_out[1]);
setpgid(status_child.pid, 0);
exec_shell(command);
return;
}
/* Parent-process. Reroute streams */
close(pipe_in[1]);
close(pipe_out[0]);
dup2(pipe_in[1], STDOUT_FILENO);
dup2(pipe_out[0], STDIN_FILENO);
status_child.stdin_fd = pipe_in[0];
child_stdin = pipe_out[1];
status_child.version = -1;
setpgid(child.pid, 0);
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
return;
default:
/* Parent-process. Reroute streams */
setup_child_cb(&status_child);
}
close(pipe_in[1]);
close(pipe_out[0]);
stdin_fd = pipe_in[0];
child_stdin = pipe_out[1];
break;
/*
* Same as start_child but starts the configured client that manages workspace
* buttons.
*
*/
void start_ws_child(char *command) {
if (command == NULL) {
return;
}
/* We set O_NONBLOCK because blocking is evil in event-driven software */
fcntl(stdin_fd, F_SETFL, O_NONBLOCK);
ws_child.stop_signal = SIGSTOP;
ws_child.cont_signal = SIGCONT;
stdin_io = smalloc(sizeof(ev_io));
ev_io_init(stdin_io, &stdin_io_first_line_cb, stdin_fd, EV_READ);
ev_io_start(main_loop, stdin_io);
int pipe_in[2]; /* pipe we read from */
spipe(pipe_in);
/* We must cleanup, if the child unexpectedly terminates */
child_sig = smalloc(sizeof(ev_child));
ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
ev_child_start(main_loop, child_sig);
ws_child.pid = sfork();
if (ws_child.pid == 0) {
/* Child-process. Reroute streams and start shell */
close(pipe_in[0]);
dup2(pipe_in[1], STDOUT_FILENO);
atexit(kill_child_at_exit);
DLOG_CHILD;
setpgid(ws_child.pid, 0);
exec_shell(command);
return;
}
/* Parent-process. Reroute streams */
close(pipe_in[1]);
ws_child.stdin_fd = pipe_in[0];
setup_child_cb(&ws_child);
}
static void child_click_events_initialize(void) {
DLOG_CHILD;
DLOG_CHILD(status_child);
if (!child.click_events_init) {
if (!status_child.click_events_init) {
yajl_gen_array_open(gen);
child_write_output();
child.click_events_init = true;
status_child.click_events_init = true;
}
}
@ -639,7 +848,7 @@ static void child_click_events_initialize(void) {
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods) {
if (!child.click_events) {
if (!status_child.click_events) {
return;
}
@ -706,35 +915,85 @@ void send_block_clicked(int button, const char *name, const char *instance, int
child_write_output();
}
static bool is_alive(i3bar_child *c) {
return c->pid > 0;
}
/*
* Returns true if the status child process is alive.
*
*/
bool status_child_is_alive(void) {
return is_alive(&status_child);
}
/*
* Returns true if the workspace child process is alive.
*
*/
bool ws_child_is_alive(void) {
return is_alive(&ws_child);
}
/*
* kill()s the child process (if any). Called when exit()ing.
*
*/
void kill_child_at_exit(void) {
DLOG_CHILD;
void kill_children_at_exit(void) {
DLOG_CHILDREN;
cont_children();
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
killpg(child.pid, child.cont_signal);
killpg(child.pid, SIGTERM);
if (is_alive(&status_child)) {
killpg(status_child.pid, SIGTERM);
}
if (is_alive(&ws_child)) {
killpg(ws_child.pid, SIGTERM);
}
}
static void cont_child(i3bar_child *c) {
if (is_alive(c) && c->cont_signal > 0 && c->stopped) {
c->stopped = false;
killpg(c->pid, c->cont_signal);
}
}
static void kill_and_wait(i3bar_child *c) {
DLOG_CHILD(*c);
if (!is_alive(c)) {
return;
}
cont_child(c);
killpg(c->pid, SIGTERM);
int status;
waitpid(c->pid, &status, 0);
cleanup(c);
}
/*
* kill()s the child process (if existent) and closes and
* free()s the stdin- and SIGCHLD-watchers
* kill()s the child process (if any) and closes and free()s the stdin- and
* SIGCHLD-watchers
*
*/
void kill_child(void) {
DLOG_CHILD;
kill_and_wait(&status_child);
}
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
killpg(child.pid, child.cont_signal);
killpg(child.pid, SIGTERM);
int status;
waitpid(child.pid, &status, 0);
cleanup();
/*
* kill()s the workspace child process (if any) and closes and free()s the
* stdin- and SIGCHLD-watchers.
* Similar to kill_child.
*
*/
void kill_ws_child(void) {
kill_and_wait(&ws_child);
}
static void stop_child(i3bar_child *c) {
if (c->stop_signal > 0 && !c->stopped) {
c->stopped = true;
killpg(c->pid, c->stop_signal);
}
}
@ -742,26 +1001,21 @@ void kill_child(void) {
* Sends a SIGSTOP to the child process (if existent)
*
*/
void stop_child(void) {
DLOG_CHILD;
if (child.stop_signal > 0 && !child.stopped) {
child.stopped = true;
killpg(child.pid, child.stop_signal);
}
void stop_children(void) {
DLOG_CHILDREN;
stop_child(&status_child);
stop_child(&ws_child);
}
/*
* Sends a SIGCONT to the child process (if existent)
*
*/
void cont_child(void) {
DLOG_CHILD;
void cont_children(void) {
DLOG_CHILDREN;
if (child.cont_signal > 0 && child.stopped) {
child.stopped = false;
killpg(child.pid, child.cont_signal);
}
cont_child(&status_child);
cont_child(&ws_child);
}
/*
@ -769,5 +1023,5 @@ void cont_child(void) {
*
*/
bool child_want_click_events(void) {
return child.click_events;
return status_child.click_events;
}

View File

@ -19,6 +19,7 @@ config_t config;
static char *cur_key;
static bool parsing_bindings;
static bool parsing_tray_outputs;
static bool parsing_padding;
/*
* Parse a key.
@ -38,12 +39,17 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t
parsing_tray_outputs = true;
}
if (strcmp(cur_key, "padding") == 0) {
parsing_padding = true;
}
return 1;
}
static int config_end_array_cb(void *params_) {
parsing_bindings = false;
parsing_tray_outputs = false;
parsing_padding = false;
return 1;
}
@ -182,11 +188,17 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
}
if (!strcmp(cur_key, "status_command")) {
DLOG("command = %.*s\n", len, val);
DLOG("status_command = %.*s\n", len, val);
sasprintf(&config.command, "%.*s", len, val);
return 1;
}
if (!strcmp(cur_key, "workspace_command")) {
DLOG("workspace_command = %.*s\n", len, val);
sasprintf(&config.workspace_command, "%.*s", len, val);
return 1;
}
if (!strcmp(cur_key, "font")) {
DLOG("font = %.*s\n", len, val);
FREE(config.fontname);
@ -329,6 +341,35 @@ static int config_integer_cb(void *params_, long long val) {
return 0;
}
if (parsing_padding) {
if (strcmp(cur_key, "x") == 0) {
DLOG("padding.x = %lld\n", val);
config.padding.x = (uint32_t)val;
return 1;
}
if (strcmp(cur_key, "y") == 0) {
DLOG("padding.y = %lld\n", val);
config.padding.y = (uint32_t)val;
return 1;
}
if (strcmp(cur_key, "width") == 0) {
DLOG("padding.width = %lld\n", val);
config.padding.width = (uint32_t)val;
return 1;
}
if (strcmp(cur_key, "height") == 0) {
DLOG("padding.height = %lld\n", val);
config.padding.height = (uint32_t)val;
return 1;
}
}
if (!strcmp(cur_key, "bar_height")) {
DLOG("bar_height = %lld\n", val);
config.bar_height = (uint32_t)val;
return 1;
}
if (!strcmp(cur_key, "tray_padding")) {
DLOG("tray_padding = %lld\n", val);
config.tray_padding = val;
@ -353,24 +394,23 @@ static int config_integer_cb(void *params_, long long val) {
/* A datastructure to pass all these callbacks to yajl */
static yajl_callbacks outputs_callbacks = {
.yajl_null = config_null_cb,
.yajl_boolean = config_boolean_cb,
.yajl_integer = config_integer_cb,
.yajl_boolean = config_boolean_cb,
.yajl_string = config_string_cb,
.yajl_end_array = config_end_array_cb,
.yajl_map_key = config_map_key_cb,
};
/*
* Start parsing the received bar configuration JSON string
* Parse the received bar configuration JSON string
*
*/
void parse_config_json(char *json) {
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
void parse_config_json(const unsigned char *json, size_t size) {
TAILQ_INIT(&(config.bindings));
TAILQ_INIT(&(config.tray_outputs));
yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper error handling for JSON parsing */
switch (state) {
@ -383,6 +423,11 @@ void parse_config_json(char *json) {
break;
}
if (config.disable_ws && config.workspace_command) {
ELOG("You have specified 'workspace_buttons no'. Your 'workspace_command %s' will be ignored.\n", config.workspace_command);
FREE(config.workspace_command);
}
yajl_free(handle);
}
@ -392,16 +437,16 @@ static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_
}
/*
* Start parsing the received bar configuration list. The only usecase right
* now is to automatically get the first bar id.
* Parse the received bar configuration list. The only usecase right now is to
* automatically get the first bar id.
*
*/
void parse_get_first_i3bar_config(char *json) {
void parse_get_first_i3bar_config(const unsigned char *json, size_t size) {
yajl_callbacks configs_callbacks = {
.yajl_string = i3bar_config_string_cb,
};
yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL);
yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_parse(handle, json, size);
yajl_free(handle);
}

View File

@ -24,14 +24,24 @@ ev_io *i3_connection;
const char *sock_path;
typedef void (*handler_t)(char *);
typedef void (*handler_t)(const unsigned char *, size_t);
/*
* Returns true when i3bar is configured to read workspace information from i3
* via JSON over the i3 IPC interface, as opposed to reading workspace
* information from the workspace_command via JSON over stdout.
*
*/
static bool i3_provides_workspaces(void) {
return !config.disable_ws && config.workspace_command == NULL;
}
/*
* Called, when we get a reply to a command from i3.
* Since i3 does not give us much feedback on commands, we do not much
*
*/
static void got_command_reply(char *reply) {
static void got_command_reply(const unsigned char *reply, size_t size) {
/* TODO: Error handling for command replies */
}
@ -39,9 +49,9 @@ static void got_command_reply(char *reply) {
* Called, when we get a reply with workspaces data
*
*/
static void got_workspace_reply(char *reply) {
static void got_workspace_reply(const unsigned char *reply, size_t size) {
DLOG("Got workspace data!\n");
parse_workspaces_json(reply);
parse_workspaces_json(reply, size);
draw_bars(false);
}
@ -50,7 +60,7 @@ static void got_workspace_reply(char *reply) {
* Since i3 does not give us much feedback on commands, we do not much
*
*/
static void got_subscribe_reply(char *reply) {
static void got_subscribe_reply(const unsigned char *reply, size_t size) {
DLOG("Got subscribe reply: %s\n", reply);
/* TODO: Error handling for subscribe commands */
}
@ -59,12 +69,12 @@ static void got_subscribe_reply(char *reply) {
* Called, when we get a reply with outputs data
*
*/
static void got_output_reply(char *reply) {
static void got_output_reply(const unsigned char *reply, size_t size) {
DLOG("Clearing old output configuration...\n");
free_outputs();
DLOG("Parsing outputs JSON...\n");
parse_outputs_json(reply);
parse_outputs_json(reply, size);
DLOG("Reconfiguring windows...\n");
reconfig_windows(false);
@ -73,8 +83,19 @@ static void got_output_reply(char *reply) {
kick_tray_clients(o_walk);
}
if (!config.disable_ws) {
if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
} else if (config.workspace_command) {
/* Communication with the workspace child is one-way. Since we called
* free_outputs() and free_workspaces() we have lost our workspace
* information which will result in no workspace buttons. A
* well-behaving client should be subscribed to output events as well
* and re-send the output information to i3bar. Even in that case
* though there is a race condition where the child can send the new
* workspace information after the output change before i3bar receives
* the output event from i3. For this reason, we re-parse the latest
* received JSON. */
repeat_last_ws_json();
}
draw_bars(false);
@ -84,10 +105,10 @@ static void got_output_reply(char *reply) {
* Called when we get the configuration for our bar instance
*
*/
static void got_bar_config(char *reply) {
static void got_bar_config(const unsigned char *reply, size_t size) {
if (!config.bar_id) {
DLOG("Received bar list \"%s\"\n", reply);
parse_get_first_i3bar_config(reply);
parse_get_first_i3bar_config(reply, size);
if (!config.bar_id) {
ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
@ -106,13 +127,14 @@ static void got_bar_config(char *reply) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
free_colors(&(config.colors));
parse_config_json(reply);
parse_config_json(reply, size);
/* Now we can actually use 'config', so let's subscribe to the appropriate
* events and request the workspaces if necessary. */
subscribe_events();
if (!config.disable_ws)
if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
/* Initialize the rest of XCB */
init_xcb_late(config.fontname);
@ -121,6 +143,7 @@ static void got_bar_config(char *reply) {
init_colors(&(config.colors));
start_child(config.command);
start_ws_child(config.workspace_command);
}
/* Data structure to easily call the reply handlers later */
@ -143,7 +166,7 @@ handler_t reply_handlers[] = {
* Called, when a workspace event arrives (i.e. the user changed the workspace)
*
*/
static void got_workspace_event(char *event) {
static void got_workspace_event(const unsigned char *event, size_t size) {
DLOG("Got workspace event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
@ -152,7 +175,7 @@ static void got_workspace_event(char *event) {
* Called, when an output event arrives (i.e. the screen configuration changed)
*
*/
static void got_output_event(char *event) {
static void got_output_event(const unsigned char *event, size_t size) {
DLOG("Got output event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
}
@ -161,9 +184,9 @@ static void got_output_event(char *event) {
* Called, when a mode event arrives (i3 changed binding mode).
*
*/
static void got_mode_event(char *event) {
static void got_mode_event(const unsigned char *event, size_t size) {
DLOG("Got mode event!\n");
parse_mode_json(event);
parse_mode_json(event, size);
draw_bars(false);
}
@ -183,11 +206,11 @@ static bool strings_differ(char *a, char *b) {
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
*
*/
static void got_bar_config_update(char *event) {
static void got_bar_config_update(const unsigned char *event, size_t size) {
/* check whether this affect this bar instance by checking the bar_id */
char *expected_id;
sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
char *found_id = strstr(event, expected_id);
char *found_id = strstr((const char *)event, expected_id);
FREE(expected_id);
if (found_id == NULL)
return;
@ -201,10 +224,12 @@ static void got_bar_config_update(char *event) {
DLOG("Received bar config update \"%s\"\n", event);
char *old_command = config.command;
char *old_workspace_command = config.workspace_command;
config.command = NULL;
config.workspace_command = NULL;
bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event);
parse_config_json(event, size);
if (old_mode != config.hide_on_modifier) {
reconfig_windows(true);
}
@ -214,13 +239,21 @@ static void got_bar_config_update(char *event) {
init_colors(&(config.colors));
/* restart status command process */
if (strings_differ(old_command, config.command)) {
if (!status_child_is_alive() || strings_differ(old_command, config.command)) {
kill_child();
clear_statusline(&statusline_head, true);
start_child(config.command);
}
free(old_command);
/* restart workspace command process */
if (!ws_child_is_alive() || strings_differ(old_workspace_command, config.workspace_command)) {
free_workspaces();
kill_ws_child();
start_ws_child(config.workspace_command);
}
free(old_workspace_command);
draw_bars(false);
}
@ -284,7 +317,7 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
/* Now that we know, what to expect, we can start read()ing the rest
* of the message */
char *buffer = smalloc(size + 1);
unsigned char *buffer = smalloc(size + 1);
rec = 0;
while (rec < size) {
@ -304,10 +337,11 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
/* And call the callback (indexed by the type) */
if (type & (1UL << 31)) {
type ^= 1UL << 31;
event_handlers[type](buffer);
event_handlers[type](buffer, size);
} else {
if (reply_handlers[type])
reply_handlers[type](buffer);
if (reply_handlers[type]) {
reply_handlers[type](buffer, size);
}
}
FREE(header);
@ -340,8 +374,9 @@ int i3_send_msg(uint32_t type, const char *payload) {
memcpy(walk, &type, sizeof(uint32_t));
walk += sizeof(uint32_t);
if (payload != NULL)
strncpy(walk, payload, len);
if (payload != NULL) {
memcpy(walk, payload, len);
}
swrite(i3_connection->fd, buffer, to_write);
@ -376,9 +411,9 @@ void destroy_connection(void) {
*
*/
void subscribe_events(void) {
if (config.disable_ws) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
} else {
if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
} else {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
}
}

View File

@ -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();

View File

@ -16,7 +16,6 @@
/* A datatype to pass through the callbacks to save the state */
struct mode_json_params {
char *json;
char *cur_key;
char *name;
bool pango_markup;
@ -96,26 +95,17 @@ static yajl_callbacks mode_callbacks = {
};
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_mode_json(char *json) {
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */
void parse_mode_json(const unsigned char *json, size_t size) {
struct mode_json_params params;
mode binding;
params.cur_key = NULL;
params.json = json;
params.mode = &binding;
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&mode_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_handle handle = yajl_alloc(&mode_callbacks, NULL, (void *)&params);
yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper error handling for JSON parsing */
switch (state) {

View File

@ -18,10 +18,8 @@
/* A datatype to pass through the callbacks to save the state */
struct outputs_json_params {
struct outputs_head *outputs;
i3_output *outputs_walk;
char *cur_key;
char *json;
bool in_rect;
};
@ -263,21 +261,17 @@ void init_outputs(void) {
}
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_outputs_json(char *json) {
void parse_outputs_json(const unsigned char *json, size_t size) {
struct outputs_json_params params;
params.outputs_walk = NULL;
params.cur_key = NULL;
params.json = json;
params.in_rect = false;
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&outputs_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, (void *)&params);
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;
}
}

View File

@ -19,7 +19,8 @@ struct workspaces_json_params {
struct ws_head *workspaces;
i3_ws *workspaces_walk;
char *cur_key;
char *json;
bool need_output;
bool parsing_rect;
};
/*
@ -71,26 +72,23 @@ static int workspaces_integer_cb(void *params_, long long val) {
return 1;
}
/* rect is unused, so we don't bother to save it */
if (!strcmp(params->cur_key, "x")) {
params->workspaces_walk->rect.x = (int)val;
FREE(params->cur_key);
return 1;
}
if (!strcmp(params->cur_key, "y")) {
params->workspaces_walk->rect.y = (int)val;
FREE(params->cur_key);
return 1;
}
if (!strcmp(params->cur_key, "width")) {
params->workspaces_walk->rect.w = (int)val;
FREE(params->cur_key);
return 1;
}
if (!strcmp(params->cur_key, "height")) {
params->workspaces_walk->rect.h = (int)val;
FREE(params->cur_key);
return 1;
}
@ -156,15 +154,16 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
sasprintf(&output_name, "%.*s", len, val);
i3_output *target = get_output_by_name(output_name);
i3_ws *ws = params->workspaces_walk;
if (target != NULL) {
params->workspaces_walk->output = target;
TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
params->workspaces_walk,
tailq);
ws->output = target;
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
}
params->need_output = false;
FREE(output_name);
FREE(params->cur_key);
return 1;
}
@ -172,28 +171,42 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
}
/*
* We hit the start of a JSON map (rect or a new output)
* We hit the start of a JSON map (rect or a new workspace)
*
*/
static int workspaces_start_map_cb(void *params_) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
i3_ws *new_workspace = NULL;
if (params->cur_key == NULL) {
new_workspace = smalloc(sizeof(i3_ws));
i3_ws *new_workspace = scalloc(1, sizeof(i3_ws));
new_workspace->num = -1;
new_workspace->name = NULL;
new_workspace->visible = 0;
new_workspace->focused = 0;
new_workspace->urgent = 0;
memset(&new_workspace->rect, 0, sizeof(rect));
new_workspace->output = NULL;
params->workspaces_walk = new_workspace;
params->need_output = true;
params->parsing_rect = false;
} else {
params->parsing_rect = true;
}
return 1;
}
static int workspaces_end_map_cb(void *params_) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
i3_ws *ws = params->workspaces_walk;
const bool parsing_rect = params->parsing_rect;
params->parsing_rect = false;
if (parsing_rect || !ws || !ws->name || !params->need_output) {
return 1;
}
ws->output = get_output_by_name("primary");
if (ws->output == NULL) {
ws->output = SLIST_FIRST(outputs);
}
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
return 1;
}
@ -216,43 +229,42 @@ static yajl_callbacks workspaces_callbacks = {
.yajl_integer = workspaces_integer_cb,
.yajl_string = workspaces_string_cb,
.yajl_start_map = workspaces_start_map_cb,
.yajl_end_map = workspaces_end_map_cb,
.yajl_map_key = workspaces_map_key_cb,
};
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_workspaces_json(char *json) {
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */
struct workspaces_json_params params;
void parse_workspaces_json(const unsigned char *json, size_t size) {
free_workspaces();
params.workspaces_walk = NULL;
params.cur_key = NULL;
params.json = json;
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
struct workspaces_json_params params = {0};
yajl_handle handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)&params);
yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper error handling for JSON parsing */
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
case yajl_status_error:
ELOG("Could not parse workspaces reply!\n");
exit(EXIT_FAILURE);
case yajl_status_error: {
unsigned char *err = yajl_get_error(handle, 1, json, size);
ELOG("Could not parse workspaces reply, error:\n%s\njson:---%s---\n", err, json);
yajl_free_error(handle, err);
if (config.workspace_command) {
kill_ws_child();
set_workspace_button_error("Could not parse workspace_command's JSON");
} else {
exit(EXIT_FAILURE);
}
break;
}
}
yajl_free(handle);
FREE(params.cur_key);
}
@ -261,14 +273,14 @@ void parse_workspaces_json(char *json) {
*
*/
void free_workspaces(void) {
i3_output *outputs_walk;
if (outputs == NULL) {
return;
}
i3_ws *ws_walk;
i3_output *outputs_walk;
SLIST_FOREACH (outputs_walk, outputs, slist) {
if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) {
i3_ws *ws_walk;
TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) {
I3STRING_FREE(ws_walk->name);
FREE(ws_walk->canonical_name);

View File

@ -59,7 +59,7 @@ xcb_visualtype_t *visual_type;
uint8_t depth;
xcb_colormap_t colormap;
/* Overall height of the bar (based on font size) */
/* Overall height of the bar */
int bar_height;
/* These are only relevant for XKB, which we only need for grabbing modifiers */
@ -180,7 +180,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
/* Draw a custom separator. */
uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2);
draw_util_text(config.separator_symbol, &output->statusline_buffer, sep_fg, bar_bg,
separator_x, logical_px(ws_voff_px), x - separator_x);
separator_x, bar_height / 2 - font.height / 2, x - separator_x);
}
}
@ -334,7 +334,7 @@ static void hide_bars(void) {
}
xcb_unmap_window(xcb_connection, walk->bar.id);
}
stop_child();
stop_children();
}
/*
@ -351,7 +351,7 @@ static void unhide_bars(void) {
uint32_t mask;
uint32_t values[5];
cont_child();
cont_children();
SLIST_FOREACH (walk, outputs, slist) {
if (walk->bar.id == XCB_NONE) {
@ -500,6 +500,49 @@ static int predict_button_width(int name_width) {
logical_px(config.ws_min_width));
}
static char *quote_workspace_name(const char *in) {
/* To properly handle workspace names with double quotes in them, we need
* to escape the double quotes. We allocate a large enough buffer (twice
* the unescaped size is always enough), then we copy character by
* character. */
const size_t namelen = strlen(in);
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *out = scalloc(2 * len, 1);
memcpy(out, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
if (in[inpos] == '"' || in[inpos] == '\\') {
out[outpos] = '\\';
outpos++;
}
out[outpos] = in[inpos];
}
out[outpos] = '"';
return out;
}
static void focus_workspace(i3_ws *ws) {
char *buffer = NULL;
if (ws->id != 0) {
/* Workspace ID has higher precedence since the workspace_command is
* allowed to change workspace names as long as it provides a valid ID. */
sasprintf(&buffer, "[con_id=%lld] focus workspace", ws->id);
goto done;
}
if (ws->canonical_name == NULL) {
return;
}
buffer = quote_workspace_name(ws->canonical_name);
done:
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
free(buffer);
}
/*
* Handle a button press event (i.e. a mouse click on one of our bars).
* We determine, whether the click occurred on a workspace button or if the scroll-
@ -620,37 +663,7 @@ static void handle_button(xcb_button_press_event_t *event) {
return;
}
/* To properly handle workspace names with double quotes in them, we need
* to escape the double quotes. Unfortunately, thats 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 were looping through the name anyway, we can save one
* strlen(). */
namelen++;
}
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *buffer = scalloc(len + num_quotes, 1);
memcpy(buffer, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
buffer[outpos] = '\\';
outpos++;
}
buffer[outpos] = utf8_name[inpos];
}
buffer[outpos] = '"';
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
free(buffer);
focus_workspace(cur_ws);
}
/*
@ -674,9 +687,9 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
}
if (num_visible == 0) {
stop_child();
stop_children();
} else {
cont_child();
cont_children();
}
}
@ -1354,8 +1367,27 @@ void init_xcb_late(char *fontname) {
/* Load the font */
font = load_font(fontname, true);
set_font(&font);
DLOG("Calculated font height: %d\n", font.height);
bar_height = font.height + 2 * logical_px(ws_voff_px);
DLOG("Calculated font-height: %d\n", font.height);
/*
* If the bar height was explicitly set (but padding was not set), use
* it. Otherwise, calculate it based on the font size.
*/
const int default_px = font.height + 2 * logical_px(ws_voff_px);
int padding_scaled =
logical_px(config.padding.y) +
logical_px(config.padding.height);
if (config.bar_height > 0 &&
config.padding.x == 0 &&
config.padding.y == 0 &&
config.padding.width == 0 &&
config.padding.height == 0) {
padding_scaled = config.bar_height - default_px;
DLOG("setting padding_scaled=%d based on bar_height=%d\n", padding_scaled, config.bar_height);
} else {
DLOG("padding: x=%d, y=%d -> padding_scaled=%d\n", config.padding.x, config.padding.y, padding_scaled);
}
bar_height = default_px + padding_scaled;
icon_size = bar_height - 2 * logical_px(config.tray_padding);
if (config.separator_symbol)
@ -1926,10 +1958,10 @@ void reconfig_windows(bool redraw_bars) {
/* Unmap the window, and draw it again when in dock mode */
umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id);
if (config.hide_on_modifier == M_DOCK) {
cont_child();
cont_children();
map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id);
} else {
stop_child();
stop_children();
}
if (config.hide_on_modifier == M_HIDE) {
@ -1973,7 +2005,7 @@ void reconfig_windows(bool redraw_bars) {
*/
static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color, color_t border_color,
int x, int width, int text_width, i3String *text) {
int height = font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1);
int height = bar_height - 2 * logical_px(1);
/* Draw the border of the button. */
draw_util_rectangle(surface, border_color, x, logical_px(1), width, height);
@ -1983,7 +2015,7 @@ static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color,
width - 2 * logical_px(1), height - 2 * logical_px(1));
draw_util_text(text, surface, fg_color, bg_color, x + (width - text_width) / 2,
logical_px(ws_voff_px), text_width);
bar_height / 2 - font.height / 2, text_width);
}
/*
@ -1998,7 +2030,7 @@ void draw_bars(bool unhide) {
i3_output *outputs_walk;
SLIST_FOREACH (outputs_walk, outputs, slist) {
int workspace_width = 0;
int workspace_width = logical_px(config.padding.x);
if (!outputs_walk->active) {
DLOG("Output %s inactive, skipping...\n", outputs_walk->name);
@ -2082,6 +2114,7 @@ void draw_bars(bool unhide) {
int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
x_dest -= logical_px(config.padding.width);
draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,

View File

@ -46,6 +46,7 @@
#include "click.h"
#include "key_press.h"
#include "floating.h"
#include "gaps.h"
#include "drag.h"
#include "configuration.h"
#include "handlers.h"

View File

@ -198,7 +198,7 @@ void cmd_focus_level(I3_CMD, const char *level);
* Implementation of 'focus'.
*
*/
void cmd_focus(I3_CMD);
void cmd_focus(I3_CMD, bool focus_workspace);
/**
* Implementation of 'fullscreen [enable|disable|toggle] [global]'.
@ -332,6 +332,12 @@ void cmd_shmlog(I3_CMD, const char *argument);
*/
void cmd_debuglog(I3_CMD, const char *argument);
/**
* Implementation of 'gaps inner|outer|top|right|bottom|left|horizontal|vertical current|all set|plus|minus|toggle <px>'
*
*/
void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value);
/**
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
*

View File

@ -420,6 +420,14 @@ Con *con_descend_tiling_focused(Con *con);
*/
Con *con_descend_direction(Con *con, direction_t direction);
/**
* Returns whether the window decoration (title bar) should be drawn into the
* X11 frame window of this container (default) or into the X11 frame window of
* the parent container (for stacked/tabbed containers).
*
*/
bool con_draw_decoration_into_frame(Con *con);
/**
* Returns a "relative" Rect which contains the amount of pixels that need to
* be added to the original Rect to get the final position (obviously the
@ -451,7 +459,7 @@ int con_border_style(Con *con);
* floating window.
*
*/
void con_set_border_style(Con *con, int border_style, int border_width);
void con_set_border_style(Con *con, border_style_t border_style, int border_width);
/**
* This function changes the layout of a given container. Use it to handle
@ -557,3 +565,9 @@ uint32_t con_rect_size_in_orientation(Con *con);
*
*/
void con_merge_into(Con *old, Con *new);
/**
* Returns true if the container is within any stacked/tabbed split container.
*
*/
bool con_inside_stacked_or_tabbed(Con *con);

View File

@ -43,11 +43,15 @@ CFGFUN(include, const char *pattern);
CFGFUN(font, const char *font);
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
CFGFUN(for_window, const char *command);
CFGFUN(gaps, const char *workspace, const char *type, const long value);
CFGFUN(smart_borders, const char *enable);
CFGFUN(smart_gaps, const char *enable);
CFGFUN(floating_minimum_size, const long width, const long height);
CFGFUN(floating_maximum_size, const long width, const long height);
CFGFUN(default_orientation, const char *orientation);
CFGFUN(workspace_layout, const char *layout);
CFGFUN(workspace_back_and_forth, const char *value);
CFGFUN(empty_workspaces, const char *value);
CFGFUN(focus_follows_mouse, const char *value);
CFGFUN(mouse_warping, const char *value);
CFGFUN(focus_wrapping, const char *value);
@ -65,6 +69,7 @@ CFGFUN(assign, const char *workspace, bool is_number);
CFGFUN(no_focus);
CFGFUN(ipc_socket, const char *path);
CFGFUN(ipc_kill_timeout, const long timeout_ms);
CFGFUN(tiling_drag, const char *value);
CFGFUN(restart_state, const char *path);
CFGFUN(popup_during_fullscreen, const char *value);
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
@ -84,6 +89,11 @@ CFGFUN(bar_hidden_state, const char *hidden_state);
CFGFUN(bar_id, const char *bar_id);
CFGFUN(bar_output, const char *output);
CFGFUN(bar_verbose, const char *verbose);
CFGFUN(bar_height, const long height);
CFGFUN(bar_padding_one, const long all);
CFGFUN(bar_padding_two, const long top_and_bottom, const long right_and_left);
CFGFUN(bar_padding_three, const long top, const long right_and_left, const long bottom);
CFGFUN(bar_padding_four, const long top, const long right, const long bottom, const long left);
CFGFUN(bar_modifier, const char *modifiers);
CFGFUN(bar_wheel_up_cmd, const char *command);
CFGFUN(bar_wheel_down_cmd, const char *command);
@ -96,6 +106,7 @@ CFGFUN(bar_tray_output, const char *output);
CFGFUN(bar_tray_padding, const long spacing_px);
CFGFUN(bar_color_single, const char *colorclass, const char *color);
CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_workspace_command, const char *command);
CFGFUN(bar_binding_mode_indicator, const char *value);
CFGFUN(bar_workspace_buttons, const char *value);
CFGFUN(bar_workspace_min_width, const long width);

View File

@ -14,6 +14,7 @@
#include "queue.h"
#include "i3.h"
#include "tiling_drag.h"
typedef struct IncludedFile IncludedFile;
typedef struct Config Config;
@ -118,6 +119,9 @@ struct Config {
/** Default orientation for new containers */
int default_orientation;
/** Init empty workspaces on startup */
bool empty_workspaces;
/** By default, focus follows mouse. If the user explicitly wants to
* turn this off (and instead rely only on the keyboard for changing
* focus), we allow them to do this with this relatively special option.
@ -265,6 +269,17 @@ struct Config {
/* The number of currently parsed barconfigs */
int number_barconfigs;
tiling_drag_t tiling_drag;
/* Gap sizes */
gaps_t gaps;
/* Should single containers on a workspace receive a border? */
smart_borders_t smart_borders;
/* Disable gaps if there is only one container on the workspace */
smart_gaps_t smart_gaps;
};
/**
@ -323,6 +338,10 @@ struct Barconfig {
* Will be passed to the shell. */
char *status_command;
/** Command that should be run to get the workspace buttons. Will be passed
* to the shell. */
char *workspace_command;
/** Font specification for all text rendered on the bar. */
char *font;
@ -352,6 +371,11 @@ struct Barconfig {
/** Enable verbose mode? Useful for debugging purposes. */
bool verbose;
/** Defines the height of the bar in pixels. */
uint32_t bar_height;
struct Rect padding;
struct bar_colors {
char *background;
char *statusline;

View File

@ -47,6 +47,7 @@ typedef struct Con Con;
typedef struct Match Match;
typedef struct Assignment Assignment;
typedef struct Window i3Window;
typedef struct gaps_t gaps_t;
typedef struct mark_t mark_t;
/******************************************************************************
@ -61,9 +62,11 @@ typedef enum { NO_ORIENTATION = 0,
VERT } orientation_t;
typedef enum { BEFORE,
AFTER } position_t;
typedef enum { BS_NORMAL = 0,
BS_NONE = 1,
BS_PIXEL = 2 } border_style_t;
typedef enum {
BS_NONE = 0,
BS_PIXEL = 1,
BS_NORMAL = 2,
} border_style_t;
/** parameter to specify whether tree_close_internal() and x_window_kill() should kill
* only this specific window or the whole X11 client */
@ -78,11 +81,20 @@ typedef enum { ADJ_NONE = 0,
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
typedef enum { SMART_BORDERS_OFF,
SMART_BORDERS_ON,
SMART_BORDERS_NO_GAPS } smart_borders_t;
typedef enum { SMART_GAPS_OFF,
SMART_GAPS_ON,
SMART_GAPS_INVERSE_OUTER } smart_gaps_t;
typedef enum { HEBM_NONE = ADJ_NONE,
HEBM_VERTICAL = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE,
HEBM_HORIZONTAL = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE,
HEBM_BOTH = HEBM_VERTICAL | HEBM_HORIZONTAL,
HEBM_SMART = (1 << 5) } hide_edge_borders_mode_t;
HEBM_SMART = (1 << 5),
HEBM_SMART_NO_GAPS = (1 << 6) } hide_edge_borders_mode_t;
typedef enum { MM_REPLACE,
MM_ADD } mark_mode_t;
@ -135,6 +147,25 @@ typedef enum {
POINTER_WARPING_NONE = 1
} warping_t;
struct gaps_t {
int inner;
int top;
int right;
int bottom;
int left;
};
typedef enum {
GAPS_INNER = (1 << 0),
GAPS_TOP = (1 << 1),
GAPS_RIGHT = (1 << 2),
GAPS_BOTTOM = (1 << 3),
GAPS_LEFT = (1 << 4),
GAPS_VERTICAL = (GAPS_TOP | GAPS_BOTTOM),
GAPS_HORIZONTAL = (GAPS_RIGHT | GAPS_LEFT),
GAPS_OUTER = (GAPS_VERTICAL | GAPS_HORIZONTAL),
} gaps_mask_t;
/**
* Focus wrapping modes.
*/
@ -202,12 +233,14 @@ struct deco_render_params {
};
/**
* Stores which workspace (by name or number) goes to which output.
* Stores which workspace (by name or number) goes to which output and its gaps config.
*
*/
struct Workspace_Assignment {
char *name;
char *output;
gaps_t gaps;
gaps_mask_t gaps_mask;
TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
};
@ -643,6 +676,9 @@ struct Con {
* workspace is not a named workspace (for named workspaces, num == -1) */
int num;
/** Only applicable for containers of type CT_WORKSPACE. */
gaps_t gaps;
struct Con *parent;
/* The position and size for this con. These coordinates are absolute. Note
@ -721,7 +757,16 @@ struct Con {
* layout in workspace_layout and creates a new split container with that
* layout whenever a new container is attached to the workspace. */
layout_t layout, last_split_layout, workspace_layout;
border_style_t border_style;
/* When the border style of a con changes because of motif hints, we don't
* want to set more decoration that the user wants. The user's preference is determined by these:
* 1. For new tiling windows, as set by `default_border`
* 2. For new floating windows, as set by `default_floating_border`
* 3. For all windows that the user runs the `border` command, whatever is
* the result of that command for that window. */
border_style_t max_user_border_style;
/** floating? (= not in tiling layout) This cannot be simply a bool
* because we want to keep track of whether the status was set by the
* application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the

41
include/gaps.h Normal file
View File

@ -0,0 +1,41 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#pragma once
#include <stdbool.h>
#include "data.h"
/**
* Calculates the effective gap sizes for a container.
*/
gaps_t calculate_effective_gaps(Con *con);
/*
* Decides whether the container should be inset.
*/
bool gaps_should_inset_con(Con *con, int children);
/*
* Returns whether the given container has an adjacent container in the
* specified direction. In other words, this returns true if and only if
* the container is not touching the edge of the screen in that direction.
*/
bool gaps_has_adjacent_container(Con *con, direction_t direction);
/**
* Returns the configured gaps for this workspace based on the workspace name,
* number, and configured workspace gap assignments.
*/
gaps_t gaps_for_workspace(Con *ws);
/**
* Re-applies all workspace gap assignments to existing workspaces after
* reloading the configuration file.
*
*/
void gaps_reapply_workspace_assignments(void);

View File

@ -133,7 +133,7 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig);
/**
* For the binding events, we send the serialized binding struct.
*/
void ipc_send_binding_event(const char *event_type, Binding *bind);
void ipc_send_binding_event(const char *event_type, Binding *bind, const char *modename);
/**
* Set the maximum duration that we allow for a connection with an unwriteable

View File

@ -9,8 +9,27 @@
*/
#pragma once
#include "all.h"
/**
* Tiling drag initiation modes.
*/
typedef enum {
TILING_DRAG_OFF = 0,
TILING_DRAG_MODIFIER = 1,
TILING_DRAG_TITLEBAR = 2,
TILING_DRAG_MODIFIER_OR_TITLEBAR = 3
} tiling_drag_t;
/**
* Returns whether there currently are any drop targets.
* Used to only initiate a drag when there is something to drop onto.
*
*/
bool has_drop_targets(void);
/**
* Initiates a mouse drag operation on a tiled window.
*
*/
void tiling_drag(Con *con, xcb_button_press_event_t *event);
void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold);

View File

@ -94,7 +94,7 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
*
*/
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
/**
* Updates the WM_CLIENT_MACHINE

View File

@ -186,3 +186,9 @@ Con *workspace_encapsulate(Con *ws);
*
*/
void workspace_move_to_output(Con *ws, Output *output);
/*
* Create all workspaces specified in the config bindings.
*
*/
void workspace_init(void);

View File

@ -20,13 +20,13 @@ extern xcb_visualtype_t *visual_type;
/* Forward declarations */
static void draw_util_set_source_color(surface_t *surface, color_t color);
#define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \
do { \
if ((surface)->id == XCB_NONE) { \
ELOG("Surface %p is not initialized, skipping drawing.\n", surface); \
return; \
} \
} while (0)
static bool surface_initialized(surface_t *surface) {
if (surface->id == XCB_NONE) {
ELOG("Surface %p is not initialized, skipping drawing.\n", surface);
return false;
}
return true;
}
/*
* Initialize the surface to represent the given drawable.
@ -131,7 +131,9 @@ color_t draw_util_hex_to_color(const char *color) {
*
*/
static void draw_util_set_source_color(surface_t *surface, color_t color) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
if (!surface_initialized(surface)) {
return;
}
cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha);
}
@ -143,7 +145,9 @@ static void draw_util_set_source_color(surface_t *surface, color_t color) {
*
*/
void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
if (!surface_initialized(surface)) {
return;
}
/* Flush any changes before we draw the text as this might use XCB directly. */
CAIRO_SURFACE_FLUSH(surface->surface);
@ -162,7 +166,9 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
*
*/
void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
if (!surface_initialized(surface)) {
return;
}
cairo_save(surface->cr);
@ -186,7 +192,9 @@ void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, i
*
*/
void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
if (!surface_initialized(surface)) {
return;
}
cairo_save(surface->cr);
@ -211,7 +219,9 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y,
*
*/
void draw_util_clear_surface(surface_t *surface, color_t color) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
if (!surface_initialized(surface)) {
return;
}
cairo_save(surface->cr);
@ -236,8 +246,10 @@ void draw_util_clear_surface(surface_t *surface, color_t color) {
*/
void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
double dest_x, double dest_y, double width, double height) {
RETURN_UNLESS_SURFACE_INITIALIZED(src);
RETURN_UNLESS_SURFACE_INITIALIZED(dest);
if (!surface_initialized(src) ||
!surface_initialized(dest)) {
return;
}
cairo_save(dest->cr);

View File

@ -21,6 +21,7 @@ static xcb_visualtype_t *root_visual_type;
static double pango_font_red;
static double pango_font_green;
static double pango_font_blue;
static double pango_font_alpha;
static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
PangoLayout *layout;
@ -102,7 +103,7 @@ static void draw_text_pango(const char *text, size_t text_len,
/* Do the drawing */
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
cairo_set_source_rgba(cr, pango_font_red, pango_font_green, pango_font_blue, pango_font_alpha);
pango_cairo_update_layout(cr, layout);
pango_layout_get_pixel_size(layout, NULL, &height);
/* Center the piece of text vertically. */
@ -303,6 +304,7 @@ void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background)
pango_font_red = foreground.red;
pango_font_green = foreground.green;
pango_font_blue = foreground.blue;
pango_font_alpha = foreground.alpha;
break;
}
}

View File

@ -30,17 +30,27 @@ SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels;
*
*/
uint32_t get_colorpixel(const char *hex) {
char strgroups[3][3] = {
char alpha[2];
if (strlen(hex) == strlen("#rrggbbaa")) {
alpha[0] = hex[7];
alpha[1] = hex[8];
} else {
alpha[0] = alpha[1] = 'F';
}
char strgroups[4][3] = {
{hex[1], hex[2], '\0'},
{hex[3], hex[4], '\0'},
{hex[5], hex[6], '\0'}};
{hex[5], hex[6], '\0'},
{alpha[0], alpha[1], '\0'}};
uint8_t r = strtol(strgroups[0], NULL, 16);
uint8_t g = strtol(strgroups[1], NULL, 16);
uint8_t b = strtol(strgroups[2], NULL, 16);
uint8_t a = strtol(strgroups[3], NULL, 16);
/* Shortcut: if our screen is true color, no need to do a roundtrip to X11 */
if (root_screen == NULL || root_screen->root_depth == 24 || root_screen->root_depth == 32) {
return (0xFFUL << 24) | (r << 16 | g << 8 | b);
return (a << 24) | (r << 16 | g << 8 | b);
}
/* Lookup this colorpixel in the cache */

View File

@ -35,7 +35,7 @@ used.
== DESCRIPTION
i3-config-wizard is started by i3 in its default config, unless ~/.i3/config
i3-config-wizard is started by i3 in its default config, unless \~/.i3/config
exists. i3-config-wizard creates a keysym based i3 config file (based on
/etc/i3/config.keycodes) in ~/.i3/config.

View File

@ -39,7 +39,7 @@ Display the <prompt> string in front of user input text field.
The prompt string is not included in the user input/command.
-f <font>::
Use the specified X11 core font (use +xfontsel+ to chose a font).
Use the specified X11 core font (use +xfontsel+ to choose a font).
-v::
Show version and exit.

View File

@ -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

View File

@ -6,17 +6,17 @@
project(
'i3',
'c',
version: '4.20.1',
version: '4.22',
default_options: [
'c_std=c11',
'warning_level=1', # enable all warnings (-Wall)
# TODO(https://github.com/i3/i3/issues/4087): switch to
# 'buildtype=debugoptimized',
],
# Ubuntu 18.04 (supported until 2023) has meson 0.45.
# Ubuntu 20.04 (supported until 2025) has meson 0.53.
# We can revisit our minimum supported meson version
# if it turns out to be too hard to maintain.
meson_version: '>=0.45.0',
meson_version: '>=0.47.0',
)
cc = meson.get_compiler('c')
@ -86,6 +86,7 @@ if get_option('docs')
'docs/wsbar',
'docs/testsuite',
'docs/i3bar-protocol',
'docs/i3bar-workspace-protocol',
'docs/layout-saving',
]
foreach m : doc_toc_inputs
@ -125,7 +126,7 @@ if get_option('docs')
endforeach
else
if run_command('[', '-f', 'docs/hacking-howto.html', ']').returncode() == 0
if run_command('[', '-f', 'docs/hacking-howto.html', ']', check: false).returncode() == 0
install_data(
[
'docs/hacking-howto.html',
@ -135,6 +136,7 @@ else
'docs/wsbar.html',
'docs/testsuite.html',
'docs/i3bar-protocol.html',
'docs/i3bar-workspace-protocol.html',
'docs/layout-saving.html',
'docs/debugging.html',
],
@ -166,6 +168,7 @@ install_data(
'docs/refcard_style.css',
'docs/logo-30.png',
'docs/layout-saving-1.png',
'docs/gaps1920.png',
],
install_dir: docdir,
)
@ -268,7 +271,7 @@ if get_option('mans')
endforeach
else
if run_command('[', '-f', 'man/i3.1', ']').returncode() == 0
if run_command('[', '-f', 'man/i3.1', ']', check: false).returncode() == 0
install_data(
[
'man/i3.1',
@ -389,6 +392,7 @@ i3srcs = [
'src/ewmh.c',
'src/fake_outputs.c',
'src/floating.c',
'src/gaps.c',
'src/handlers.c',
'src/ipc.c',
'src/key_press.c',

View File

@ -43,6 +43,7 @@ state INITIAL:
'title_window_icon' -> TITLE_WINDOW_ICON
'mode' -> MODE
'bar' -> BAR
'gaps' -> GAPS
state CRITERIA:
ctype = 'class' -> CRITERION
@ -56,6 +57,8 @@ state CRITERIA:
ctype = 'urgent' -> CRITERION
ctype = 'workspace' -> CRITERION
ctype = 'machine' -> CRITERION
ctype = 'floating_from' -> CRITERION_FROM
ctype = 'tiling_from' -> CRITERION_FROM
ctype = 'tiling', 'floating', 'all'
-> call cmd_criteria_add($ctype, NULL); CRITERIA
']' -> call cmd_criteria_match_windows(); INITIAL
@ -63,6 +66,22 @@ state CRITERIA:
state CRITERION:
'=' -> CRITERION_STR
state CRITERION_FROM:
'=' -> CRITERION_FROM_STR_START
state CRITERION_FROM_STR_START:
'"' -> CRITERION_FROM_STR
kind = 'auto', 'user'
-> call cmd_criteria_add($ctype, $kind); CRITERIA
state CRITERION_FROM_STR:
kind = 'auto', 'user'
-> CRITERION_FROM_STR_END
state CRITERION_FROM_STR_END:
'"'
-> call cmd_criteria_add($ctype, $kind); CRITERIA
state CRITERION_STR:
cvalue = word
-> call cmd_criteria_add($ctype, $cvalue); CRITERIA
@ -101,6 +120,29 @@ state BORDER_WIDTH:
border_width = number
-> call cmd_border($border_style, &border_width)
# gaps inner|outer|horizontal|vertical|top|right|bottom|left [current] [set|plus|minus|toggle] <px>
state GAPS:
type = 'inner', 'outer', 'horizontal', 'vertical', 'top', 'right', 'bottom', 'left'
-> GAPS_WITH_TYPE
state GAPS_WITH_TYPE:
scope = 'current', 'all'
-> GAPS_WITH_SCOPE
state GAPS_WITH_SCOPE:
mode = 'plus', 'minus', 'set', 'toggle'
-> GAPS_WITH_MODE
state GAPS_WITH_MODE:
value = word
-> GAPS_END
state GAPS_END:
'px'
->
end
-> call cmd_gaps($type, $scope, $mode, $value)
# layout default|stacked|stacking|tabbed|splitv|splith
# layout toggle [split|all]
state LAYOUT:
@ -144,6 +186,7 @@ state WORKSPACE_NUMBER:
# focus output <output>
# focus tiling|floating|mode_toggle
# focus parent|child
# focus workspace
# focus
state FOCUS:
direction = 'left', 'right', 'up', 'down'
@ -156,8 +199,10 @@ state FOCUS:
-> call cmd_focus_window_mode($window_mode)
level = 'parent', 'child'
-> call cmd_focus_level($level)
workspace = 'workspace'
-> call cmd_focus(1)
end
-> call cmd_focus()
-> call cmd_focus(0)
state FOCUS_AUTO:
'sibling'

View File

@ -25,6 +25,9 @@ state INITIAL:
'bar' -> BARBRACE
'font' -> FONT
'mode' -> MODENAME
'gaps' -> GAPS
'smart_borders' -> SMART_BORDERS
'smart_gaps' -> SMART_GAPS
'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH
'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH
'floating_modifier' -> FLOATING_MODIFIER
@ -53,6 +56,7 @@ state INITIAL:
'ipc_kill_timeout' -> IPC_KILL_TIMEOUT
'restart_state' -> RESTART_STATE
'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN
'tiling_drag' -> TILING_DRAG
exectype = 'exec_always', 'exec' -> EXEC
colorclass = 'client.background'
-> COLOR_SINGLE
@ -64,6 +68,38 @@ state IGNORE_LINE:
line
-> INITIAL
# gaps inner|outer|horizontal|vertical|top|right|bottom|left <gap_size>[px]
state GAPS:
scope = 'inner', 'outer', 'horizontal', 'vertical', 'top', 'right', 'bottom', 'left'
-> GAPS_WITH_SCOPE
state GAPS_WITH_SCOPE:
value = number
-> GAPS_END
state GAPS_END:
'px'
->
end
-> call cfg_gaps($workspace, $scope, &value)
# smart_borders true|false
# smart_borders no_gaps
state SMART_BORDERS:
enabled = '1', 'yes', 'true', 'on', 'enable', 'active'
-> call cfg_smart_borders($enabled)
enabled = 'no_gaps'
-> call cfg_smart_borders($enabled)
# smart_gaps on|off|inverse_outer
state SMART_GAPS:
enabled = '1', 'yes', 'true', 'on', 'enable', 'active'
-> call cfg_smart_gaps($enabled)
enabled = '0', 'no', 'false', 'off', 'disable', 'inactive'
-> call cfg_smart_gaps($enabled)
enabled = 'inverse_outer'
-> call cfg_smart_gaps($enabled)
# include <pattern>
state INCLUDE:
pattern = string
@ -134,10 +170,10 @@ state DEFAULT_BORDER_PIXELS_PX:
end
-> call cfg_default_border($windowtype, $border, &width)
# hide_edge_borders <none|vertical|horizontal|both|smart>
# hide_edge_borders <none|vertical|horizontal|both|smart|smart_no_gaps>
# also hide_edge_borders <bool> for compatibility
state HIDE_EDGE_BORDERS:
hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart'
hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart'
-> call cfg_hide_edge_borders($hide_borders)
hide_borders = '1', 'yes', 'true', 'on', 'enable', 'active'
-> call cfg_hide_edge_borders($hide_borders)
@ -296,13 +332,16 @@ state FOCUS_ON_WINDOW_ACTIVATION:
-> call cfg_focus_on_window_activation($mode)
# workspace <workspace> output <output>
# workspace <workspace> gaps inner|outer <px>
state WORKSPACE:
workspace = word
-> WORKSPACE_OUTPUT
-> WORKSPACE_COMMAND
state WORKSPACE_OUTPUT:
state WORKSPACE_COMMAND:
'output'
-> WORKSPACE_OUTPUT_WORD
'gaps'
-> GAPS
state WORKSPACE_OUTPUT_WORD:
output = word
@ -330,6 +369,18 @@ state POPUP_DURING_FULLSCREEN:
value = 'ignore', 'leave_fullscreen', 'smart'
-> call cfg_popup_during_fullscreen($value)
state TILING_DRAG_MODE:
value = 'modifier', 'titlebar'
->
end
-> call cfg_tiling_drag($value)
state TILING_DRAG:
off = '0', 'no', 'false', 'off', 'disable', 'inactive'
-> call cfg_tiling_drag($off)
value = 'modifier', 'titlebar'
-> TILING_DRAG_MODE
# client.background <hexcolor>
state COLOR_SINGLE:
color = word
@ -477,6 +528,7 @@ state BAR:
'set' -> BAR_IGNORE_LINE
'i3bar_command' -> BAR_BAR_COMMAND
'status_command' -> BAR_STATUS_COMMAND
'workspace_command' -> BAR_WORKSPACE_COMMAND
'socket_path' -> BAR_SOCKET_PATH
'mode' -> BAR_MODE
'hidden_state' -> BAR_HIDDEN_STATE
@ -497,6 +549,8 @@ state BAR:
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
'verbose' -> BAR_VERBOSE
'height' -> BAR_HEIGHT
'padding' -> BAR_PADDING
'colors' -> BAR_COLORS_BRACE
'}'
-> call cfg_bar_finish(); INITIAL
@ -514,6 +568,10 @@ state BAR_STATUS_COMMAND:
command = string
-> call cfg_bar_status_command($command); BAR
state BAR_WORKSPACE_COMMAND:
command = string
-> call cfg_bar_workspace_command($command); BAR
state BAR_SOCKET_PATH:
path = string
-> call cfg_bar_socket_path($path); BAR
@ -565,7 +623,7 @@ state BAR_POSITION:
-> call cfg_bar_position($position); BAR
state BAR_OUTPUT:
output = string
output = word
-> call cfg_bar_output($output); BAR
state BAR_TRAY_OUTPUT:
@ -620,6 +678,44 @@ state BAR_VERBOSE:
value = word
-> call cfg_bar_verbose($value); BAR
state BAR_HEIGHT:
value = number
-> call cfg_bar_height(&value); BAR
state BAR_PADDING:
top_or_all = number
-> BAR_PADDING_TOP
state BAR_PADDING_TOP:
'px'
->
right_or_right_and_left = number
-> BAR_PADDING_RIGHT
end
-> call cfg_bar_padding_one(&top_or_all); BAR
state BAR_PADDING_RIGHT:
'px'
->
bottom = number
-> BAR_PADDING_BOTTOM
end
-> call cfg_bar_padding_two(&top_or_all, &right_or_right_and_left); BAR
state BAR_PADDING_BOTTOM:
'px'
->
left = number
-> BAR_PADDING_LEFT
end
-> call cfg_bar_padding_three(&top_or_all, &right_or_right_and_left, &bottom); BAR
state BAR_PADDING_LEFT:
'px'
->
end
-> call cfg_bar_padding_four(&top_or_all, &right_or_right_and_left, &bottom, &left); BAR
state BAR_COLORS_BRACE:
end
->

View File

@ -0,0 +1 @@
fix regression with i3bar's output nonprimary

View File

@ -1 +0,0 @@
Do not replace existing IPC socket on start

View File

@ -1 +0,0 @@
fix focus when moving container between outputs with mouse warp and focus_follows_mouse

View File

@ -1 +0,0 @@
Fix endless loop with transient_for windows

View File

@ -1 +0,0 @@
fix wrong failed reply on move workspace to output

View File

@ -1 +0,0 @@
changed WM registration selection from WM_S_S<screen> to WM_S<screen>

View File

@ -1 +0,0 @@
avoid graphics artifacts when changing the layout tree by initializing surfaces to all black

View File

@ -1 +0,0 @@
Fix segfault if command in bindsym is empty

View File

@ -1 +0,0 @@
update parent split con titles when child con swaps position with another child con

View File

@ -1 +0,0 @@
Refuse to start without valid IPC socket

View File

@ -0,0 +1 @@
add workspace_command option in i3bar

View File

@ -1 +0,0 @@
Add client.focused_tab_title color option

View File

@ -0,0 +1 @@
add "focus workspace" command

View File

@ -1 +0,0 @@
Add support for multiple outputs in focus command

View File

@ -1 +0,0 @@
Allow moving tiling windows with the mouse

View File

@ -1 +0,0 @@
Add title_window_icon toggle

View File

@ -3,8 +3,8 @@
set -eu
export RELEASE_VERSION="4.20"
export PREVIOUS_VERSION="4.19.2"
export RELEASE_VERSION="4.22"
export PREVIOUS_VERSION="4.21.1"
export RELEASE_BRANCH="next"
if [ ! -e "../i3.github.io" ]
@ -20,6 +20,12 @@ then
exit 1
fi
if git diff-files --quiet --exit-code debian/changelog
then
echo "Expected debian/changelog to be changed (containing the changelog for ${RELEASE_VERSION})."
exit 1
fi
if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
then
echo "RELEASE-NOTES-${RELEASE_VERSION} not found. Here is the output from the generator:"
@ -108,7 +114,7 @@ cp "${STARTDIR}/debian/changelog" i3/debian/changelog
cat > ${TMPDIR}/Dockerfile <<EOT
FROM debian:sid
RUN sed -i 's,^deb \(.*\),deb \1\ndeb-src \1,g' /etc/apt/sources.list
RUN echo deb-src http://deb.debian.org/debian sid main > /etc/apt/sources.list
RUN apt-get update && apt-get install -y dpkg-dev devscripts
COPY i3/i3-${RELEASE_VERSION}.tar.xz /usr/src/i3-wm_${RELEASE_VERSION}.orig.tar.xz
WORKDIR /usr/src/
@ -158,7 +164,7 @@ git add downloads/i3-${RELEASE_VERSION}.tar.xz*
cp ${TMPDIR}/i3/RELEASE-NOTES-${RELEASE_VERSION} downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
git add downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
sed -i "s,<h2>Documentation for i3 v[^<]*</h2>,<h2>Documentation for i3 v${RELEASE_VERSION}</h2>,g" docs/index.html
sed -i "s,<span style=\"margin-left: 2em; color: #c0c0c0\">[^<]*</span>,<span style=\"margin-left: 2em; color: #c0c0c0\">${RELEASE_VERSION}</span>,g" index.html
sed -i "s,\(span class=\"version\">\)[^<]*\(</span>\),\1${RELEASE_VERSION}\2,g" index.html
sed -i "s,The current stable version is .*$,The current stable version is ${RELEASE_VERSION}.,g" downloads/index.html
sed -i "s,<tbody>,<tbody>\n <tr>\n <td>${RELEASE_VERSION}</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.xz\">i3-${RELEASE_VERSION}.tar.xz</a></td>\n <td>$(LC_ALL=en_US.UTF-8 ls -lh ../i3/i3-${RELEASE_VERSION}.tar.xz | awk -F " " {'print $5'} | sed 's/K$/ KiB/g' | sed 's/M$/ MiB/g')</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.xz.asc\">signature</a></td>\n <td>$(date +'%Y-%m-%d')</td>\n <td><a href=\"/downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt\">release notes</a></td>\n </tr>\n,g" downloads/index.html
@ -184,7 +190,7 @@ git commit -a -m "update docs for ${RELEASE_VERSION}"
git remote remove origin
git remote add origin git@github.com:i3/i3.github.io.git
git config --add remote.origin.push "+refs/heads/master:refs/heads/master"
git config --add remote.origin.push "+refs/heads/main:refs/heads/main"
################################################################################
# Section 4: prepare release announcement email
@ -220,9 +226,6 @@ echo ""
echo " cd ${TMPDIR}/i3.github.io"
echo " git push"
echo ""
echo " cd ${TMPDIR}/debian"
echo " dput"
echo ""
echo " cd ${TMPDIR}"
echo " sendmail -t < email.txt"
echo ""

View File

@ -843,6 +843,10 @@ CommandResult *run_binding(Binding *bind, Con *con) {
sasprintf(&command, "[con_id=\"%p\"] %s", con, bind->command);
Binding *bind_cp = binding_copy(bind);
/* The "mode" command might change the current mode, so back it up to
* correctly produce an event later. */
char *modename = sstrdup(current_binding_mode);
CommandResult *result = parse_command(command, NULL, NULL);
free(command);
@ -868,7 +872,8 @@ CommandResult *run_binding(Binding *bind, Con *con) {
free(pageraction);
}
ipc_send_binding_event("run", bind_cp);
ipc_send_binding_event("run", bind_cp, modename);
FREE(modename);
binding_free(bind_cp);
return result;

View File

@ -39,6 +39,9 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
case BORDER_BOTTOM:
search_direction = D_DOWN;
break;
default:
ELOG("BUG: invalid border value %d\n", border);
return false;
}
bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
@ -141,6 +144,12 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click
return false;
}
static void allow_replay_pointer(xcb_timestamp_t time) {
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, time);
xcb_flush(conn);
tree_render();
}
/*
* Being called by handle_button_press, this function calls the appropriate
* functions for resizing/dragging.
@ -152,8 +161,10 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
DLOG("type = %d, name = %s\n", con->type, con->name);
/* dont handle dockarea cons, they must not be focused */
if (con->parent->type == CT_DOCKAREA)
goto done;
if (con->parent->type == CT_DOCKAREA) {
allow_replay_pointer(event->time);
return;
}
/* if the user has bound an action to this click, it should override the
* default behavior. */
@ -173,7 +184,8 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
/* There is no default behavior for button release events so we are done. */
if (event->response_type == XCB_BUTTON_RELEASE) {
goto done;
allow_replay_pointer(event->time);
return;
}
/* Any click in a workspace should focus that workspace. If the
@ -184,8 +196,10 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
if (!ws) {
ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head));
if (!ws)
goto done;
if (!ws) {
allow_replay_pointer(event->time);
return;
}
}
/* get the floating con */
@ -212,13 +226,19 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
Con *next = get_tree_next_sibling(current, direction);
con_activate(con_descend_focused(next ? next : current));
goto done;
allow_replay_pointer(event->time);
return;
}
/* 2: floating modifier pressed, initiate a drag */
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1 && !floatingcon) {
tiling_drag(con, event);
goto done;
if (mod_pressed && is_left_click && !floatingcon &&
(config.tiling_drag == TILING_DRAG_MODIFIER ||
config.tiling_drag == TILING_DRAG_MODIFIER_OR_TITLEBAR) &&
has_drop_targets()) {
const bool use_threshold = !mod_pressed;
tiling_drag(con, event, use_threshold);
allow_replay_pointer(event->time);
return;
}
/* 3: focus this con or one of its children. */
@ -262,8 +282,10 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
is_left_or_right_click) {
/* try tiling resize, but continue if it doesnt work */
DLOG("tiling resize with fallback\n");
if (tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused))
goto done;
if (tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused)) {
allow_replay_pointer(event->time);
return;
}
}
if (dest == CLICK_DECORATION && is_right_click) {
@ -285,13 +307,20 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
return;
}
goto done;
allow_replay_pointer(event->time);
return;
}
/* 8: floating modifier pressed, initiate a drag */
if ((mod_pressed || dest == CLICK_DECORATION) && event->detail == XCB_BUTTON_INDEX_1) {
tiling_drag(con, event);
goto done;
/* 8: floating modifier pressed, or click in titlebar, initiate a drag */
if (is_left_click &&
((config.tiling_drag == TILING_DRAG_TITLEBAR && dest == CLICK_DECORATION) ||
(config.tiling_drag == TILING_DRAG_MODIFIER_OR_TITLEBAR &&
(mod_pressed || dest == CLICK_DECORATION))) &&
has_drop_targets()) {
allow_replay_pointer(event->time);
const bool use_threshold = !mod_pressed;
tiling_drag(con, event, use_threshold);
return;
}
/* 9: floating modifier pressed, initiate a resize */
@ -312,10 +341,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused);
}
done:
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn);
tree_render();
allow_replay_pointer(event->time);
}
/*
@ -381,13 +407,20 @@ void handle_button_press(xcb_button_press_event_t *event) {
}
/* Check if the click was on the decoration of a child */
Con *child;
TAILQ_FOREACH_REVERSE (child, &(con->nodes_head), nodes_head, nodes) {
if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
continue;
if (con->window != NULL) {
if (rect_contains(con->deco_rect, event->event_x, event->event_y)) {
route_click(con, event, mod_pressed, CLICK_DECORATION);
return;
}
} else {
Con *child;
TAILQ_FOREACH_REVERSE (child, &(con->nodes_head), nodes_head, nodes) {
if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
continue;
route_click(child, event, mod_pressed, CLICK_DECORATION);
return;
route_click(child, event, mod_pressed, CLICK_DECORATION);
return;
}
}
if (event->child != XCB_NONE) {

View File

@ -747,6 +747,8 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
return;
}
/* User changed the border */
current->con->max_user_border_style = border_style;
const int con_border_width = border_width_from_style(border_style, border_width, current->con);
con_set_border_style(current->con, border_style, con_border_width);
}
@ -1030,11 +1032,16 @@ typedef struct user_output_name {
typedef TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names_head;
static void user_output_names_add(user_output_names_head *list, const char *name) {
if (strcmp(name, "next") == 0) {
/* "next" here works like a wildcard: It "expands" to all available
* outputs. */
const bool get_non_primary = (strcasecmp("nonprimary", name) == 0);
if (get_non_primary || strcmp(name, "next") == 0) {
/* "next" (or "nonprimary") here work like a wildcard: It "expands" to
* all available (or non-primary) outputs. */
Output *output;
TAILQ_FOREACH (output, &outputs, outputs) {
if (get_non_primary && output->primary) {
continue;
}
user_output_name *co = scalloc(sizeof(user_output_name), 1);
co->name = sstrdup(output_primary_name(output));
TAILQ_INSERT_TAIL(list, co, user_output_names);
@ -1318,7 +1325,7 @@ void cmd_focus_direction(I3_CMD, const char *direction_str) {
HANDLE_EMPTY_MATCH;
CMD_FOCUS_WARN_CHILDREN;
direction_t direction;
direction_t direction = D_LEFT;
position_t position;
bool auto_direction = true;
if (strcmp(direction_str, "prev") == 0) {
@ -1452,7 +1459,7 @@ void cmd_focus_level(I3_CMD, const char *level) {
* Implementation of 'focus'.
*
*/
void cmd_focus(I3_CMD) {
void cmd_focus(I3_CMD, bool focus_workspace) {
DLOG("current_match = %p\n", current_match);
if (match_is_empty(current_match)) {
@ -1474,11 +1481,12 @@ void cmd_focus(I3_CMD) {
Con *ws = con_get_workspace(current->con);
/* If no workspace could be found, this was a dock window.
* Just skip it, you cannot focus dock windows. */
if (!ws)
if (!ws) {
continue;
}
/* In case this is a scratchpad window, call scratchpad_show(). */
if (ws == __i3_scratch) {
if (ws == __i3_scratch && !focus_workspace) {
scratchpad_show(current->con);
/* While for the normal focus case we can change focus multiple
* times and only a single window ends up focused, we could show
@ -1486,8 +1494,15 @@ void cmd_focus(I3_CMD) {
break;
}
LOG("focusing %p / %s\n", current->con, current->con->name);
con_activate_unblock(current->con);
if (focus_workspace) {
/* Show the workspace of the matched container, without necessarily
* focusing it. */
LOG("focusing workspace %p / %s - %p / %s\n", current->con, current->con->name, ws, ws->name);
workspace_show(ws);
} else {
LOG("focusing %p / %s\n", current->con, current->con->name);
con_activate_unblock(current->con);
}
}
cmd_output->needs_tree_render = true;
@ -2372,3 +2387,171 @@ void cmd_debuglog(I3_CMD, const char *argument) {
// XXX: default reply for now, make this a better reply
ysuccess(true);
}
static int *gaps_inner(gaps_t *gaps) {
return &(gaps->inner);
}
static int *gaps_top(gaps_t *gaps) {
return &(gaps->top);
}
static int *gaps_left(gaps_t *gaps) {
return &(gaps->left);
}
static int *gaps_bottom(gaps_t *gaps) {
return &(gaps->bottom);
}
static int *gaps_right(gaps_t *gaps) {
return &(gaps->right);
}
typedef int *(*gap_accessor)(gaps_t *);
static bool gaps_update(gap_accessor get, const char *scope, const char *mode, int pixels) {
DLOG("gaps_update(scope=%s, mode=%s, pixels=%d)\n", scope, mode, pixels);
Con *workspace = con_get_workspace(focused);
const int global_gap_size = *get(&(config.gaps));
int current_value = global_gap_size;
if (strcmp(scope, "current") == 0) {
current_value += *get(&(workspace->gaps));
}
DLOG("global_gap_size=%d, current_value=%d\n", global_gap_size, current_value);
bool reset = false;
if (strcmp(mode, "plus") == 0)
current_value += pixels;
else if (strcmp(mode, "minus") == 0)
current_value -= pixels;
else if (strcmp(mode, "set") == 0) {
current_value = pixels;
reset = true;
} else if (strcmp(mode, "toggle") == 0) {
current_value = !current_value * pixels;
reset = true;
} else {
ELOG("Invalid mode %s when changing gaps", mode);
return false;
}
/* See https://github.com/Airblader/i3/issues/262 */
int min_value = 0;
const bool is_outer = get(&(config.gaps)) != gaps_inner(&(config.gaps));
if (is_outer) {
/* Outer gaps can compensate inner gaps. */
if (strcmp(scope, "all") == 0) {
min_value = -config.gaps.inner;
} else {
min_value = -config.gaps.inner - workspace->gaps.inner;
}
}
if (current_value < min_value) {
current_value = min_value;
}
if (strcmp(scope, "all") == 0) {
Con *output = NULL;
TAILQ_FOREACH (output, &(croot->nodes_head), nodes) {
Con *cur_ws = NULL;
Con *content = output_get_content(output);
TAILQ_FOREACH (cur_ws, &(content->nodes_head), nodes) {
int *gaps_value = get(&(cur_ws->gaps));
DLOG("current gaps_value = %d\n", *gaps_value);
if (reset) {
*gaps_value = 0;
} else {
int max_compensate = 0;
if (is_outer) {
max_compensate = config.gaps.inner;
}
if (*gaps_value + current_value + max_compensate < 0) {
/* Enforce new per-workspace gap size minimum value (in case
current_value is smaller than before): the workspace can at most
have a negative gap size of -current_value - max_compensate. */
*gaps_value = -current_value - max_compensate;
}
}
DLOG("gaps_value after fix = %d\n", *gaps_value);
}
}
*get(&(config.gaps)) = current_value;
DLOG("global gaps value after fix = %d\n", *get(&(config.gaps)));
} else {
int *gaps_value = get(&(workspace->gaps));
*gaps_value = current_value - global_gap_size;
}
return true;
}
/**
* Implementation of 'gaps inner|outer|top|right|bottom|left|horizontal|vertical current|all set|plus|minus|toggle <px>'
*
*/
void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value) {
int pixels = logical_px(atoi(value));
if (!strcmp(type, "inner")) {
if (!gaps_update(gaps_inner, scope, mode, pixels)) {
goto error;
}
/* Update all workspaces with a no-op change (plus 0) so that the
* minimum value is re-calculated and applied as a side effect. */
if (!gaps_update(gaps_top, "all", "plus", 0) ||
!gaps_update(gaps_bottom, "all", "plus", 0) ||
!gaps_update(gaps_right, "all", "plus", 0) ||
!gaps_update(gaps_left, "all", "plus", 0)) {
goto error;
}
} else if (!strcmp(type, "outer")) {
if (!gaps_update(gaps_top, scope, mode, pixels) ||
!gaps_update(gaps_bottom, scope, mode, pixels) ||
!gaps_update(gaps_right, scope, mode, pixels) ||
!gaps_update(gaps_left, scope, mode, pixels)) {
goto error;
}
} else if (!strcmp(type, "vertical")) {
if (!gaps_update(gaps_top, scope, mode, pixels) ||
!gaps_update(gaps_bottom, scope, mode, pixels)) {
goto error;
}
} else if (!strcmp(type, "horizontal")) {
if (!gaps_update(gaps_right, scope, mode, pixels) ||
!gaps_update(gaps_left, scope, mode, pixels)) {
goto error;
}
} else if (!strcmp(type, "top")) {
if (!gaps_update(gaps_top, scope, mode, pixels)) {
goto error;
}
} else if (!strcmp(type, "bottom")) {
if (!gaps_update(gaps_bottom, scope, mode, pixels)) {
goto error;
}
} else if (!strcmp(type, "right")) {
if (!gaps_update(gaps_right, scope, mode, pixels)) {
goto error;
}
} else if (!strcmp(type, "left")) {
if (!gaps_update(gaps_left, scope, mode, pixels)) {
goto error;
}
} else {
ELOG("Invalid type %s when changing gaps", type);
goto error;
}
cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply
ysuccess(true);
return;
error:
ysuccess(false);
}

109
src/con.c
View File

@ -41,7 +41,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
new->type = CT_CON;
new->window = window;
new->border_style = config.default_border;
new->border_style = new->max_user_border_style = config.default_border;
new->current_border_width = -1;
new->window_icon_padding = -1;
if (window) {
@ -618,7 +618,10 @@ bool con_is_docked(Con *con) {
*
*/
Con *con_inside_floating(Con *con) {
assert(con != NULL);
if (con == NULL) {
return NULL;
}
if (con->type == CT_FLOATING_CON)
return con;
@ -1685,17 +1688,34 @@ Con *con_descend_direction(Con *con, direction_t direction) {
return con_descend_direction(most, direction);
}
static bool has_outer_gaps(gaps_t gaps) {
return gaps.top > 0 ||
gaps.right > 0 ||
gaps.bottom > 0 ||
gaps.left > 0;
}
/*
* Returns a "relative" Rect which contains the amount of pixels that need to
* be added to the original Rect to get the final position (obviously the
* amount of pixels for normal, 1pixel and borderless are different).
* Returns whether the window decoration (title bar) should be drawn into the
* X11 frame window of this container (default) or into the X11 frame window of
* the parent container (for stacked/tabbed containers).
*
*/
Rect con_border_style_rect(Con *con) {
if (config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) {
if (!con_is_floating(con)) {
bool con_draw_decoration_into_frame(Con *con) {
return con_is_leaf(con) &&
con_border_style(con) == BS_NORMAL &&
(con->parent == NULL ||
(con->parent->layout != L_TABBED &&
con->parent->layout != L_STACKED));
}
static Rect con_border_style_rect_without_title(Con *con) {
if ((config.smart_borders == SMART_BORDERS_ON && con_num_visible_children(con_get_workspace(con)) <= 1) ||
(config.smart_borders == SMART_BORDERS_NO_GAPS && !has_outer_gaps(calculate_effective_gaps(con))) ||
(config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) ||
(config.hide_edge_borders == HEBM_SMART_NO_GAPS && con_num_visible_children(con_get_workspace(con)) <= 1 && !has_outer_gaps(calculate_effective_gaps(con)))) {
if (!con_is_floating(con))
return (Rect){0, 0, 0, 0};
}
}
adjacent_t borders_to_hide = ADJ_NONE;
@ -1720,7 +1740,13 @@ Rect con_border_style_rect(Con *con) {
result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
}
borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
/* If hide_edge_borders is set to no_gaps and it did not pass the no border check, show all borders */
if (config.hide_edge_borders == HEBM_SMART_NO_GAPS) {
borders_to_hide = con_adjacent_borders(con) & HEBM_NONE;
} else {
borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
}
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
result.x -= border_width;
result.width += border_width;
@ -1738,6 +1764,23 @@ Rect con_border_style_rect(Con *con) {
return result;
}
/*
* Returns a "relative" Rect which contains the amount of pixels that need to
* be added to the original Rect to get the final position (obviously the
* amount of pixels for normal, 1pixel and borderless are different).
*
*/
Rect con_border_style_rect(Con *con) {
Rect result = con_border_style_rect_without_title(con);
if (con_border_style(con) == BS_NORMAL &&
con_draw_decoration_into_frame(con)) {
const int deco_height = render_deco_height();
result.y += deco_height;
result.height -= deco_height;
}
return result;
}
/*
* Returns adjacent borders of the window. We need this if hide_edge_borders is
* enabled.
@ -1777,14 +1820,19 @@ int con_border_style(Con *con) {
return BS_NONE;
}
if (con->parent->layout == L_STACKED)
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
if (con->parent != NULL) {
if (con->parent->layout == L_STACKED) {
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
}
if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL)
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL) {
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
}
if (con->parent->type == CT_DOCKAREA)
return BS_NONE;
if (con->parent->type == CT_DOCKAREA) {
return BS_NONE;
}
}
return con->border_style;
}
@ -1794,7 +1842,11 @@ int con_border_style(Con *con) {
* floating window.
*
*/
void con_set_border_style(Con *con, int border_style, int border_width) {
void con_set_border_style(Con *con, border_style_t border_style, int border_width) {
if (border_style > con->max_user_border_style) {
border_style = con->max_user_border_style;
}
/* Handle the simple case: non-floating containerns */
if (!con_is_floating(con)) {
con->border_style = border_style;
@ -1807,27 +1859,19 @@ void con_set_border_style(Con *con, int border_style, int border_width) {
* con->rect represent the absolute position of the window (same for
* parent). Then, we change the border style and subtract the new border
* pixels. For the parent, we do the same also for the decoration. */
DLOG("This is a floating container\n");
Con *parent = con->parent;
Rect bsr = con_border_style_rect(con);
int deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
con->rect = rect_add(con->rect, bsr);
parent->rect = rect_add(parent->rect, bsr);
parent->rect.y += deco_height;
parent->rect.height -= deco_height;
/* Change the border style, get new border/decoration values. */
con->border_style = border_style;
con->current_border_width = border_width;
bsr = con_border_style_rect(con);
deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
con->rect = rect_sub(con->rect, bsr);
parent->rect = rect_sub(parent->rect, bsr);
parent->rect.y -= deco_height;
parent->rect.height += deco_height;
}
/*
@ -2538,3 +2582,18 @@ void con_merge_into(Con *old, Con *new) {
tree_close_internal(old, DONT_KILL_WINDOW, false);
}
/*
* Returns true if the container is within any stacked/tabbed split container.
*
*/
bool con_inside_stacked_or_tabbed(Con *con) {
if (con->parent == NULL) {
return false;
}
if (con->parent->layout == L_STACKED ||
con->parent->layout == L_TABBED) {
return true;
}
return con_inside_stacked_or_tabbed(con->parent);
}

View File

@ -105,6 +105,7 @@ static void free_configuration(void) {
FREE(barconfig->outputs);
FREE(barconfig->socket_path);
FREE(barconfig->status_command);
FREE(barconfig->workspace_command);
FREE(barconfig->i3bar_command);
FREE(barconfig->font);
FREE(barconfig->colors.background);
@ -209,6 +210,8 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
config.show_marks = true;
config.empty_workspaces = false;
config.default_border = BS_NORMAL;
config.default_floating_border = BS_NORMAL;
config.default_border_width = logical_px(2);
@ -216,12 +219,20 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
/* Set default_orientation to NO_ORIENTATION for auto orientation. */
config.default_orientation = NO_ORIENTATION;
config.gaps.inner = 0;
config.gaps.top = 0;
config.gaps.right = 0;
config.gaps.bottom = 0;
config.gaps.left = 0;
/* Set default urgency reset delay to 500ms */
if (config.workspace_urgency_timer == 0)
config.workspace_urgency_timer = 0.5;
config.focus_wrapping = FOCUS_WRAPPING_ON;
config.tiling_drag = TILING_DRAG_MODIFIER;
FREE(current_configpath);
current_configpath = get_config_path(override_configpath, true);
if (current_configpath == NULL) {
@ -273,15 +284,26 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
set_font(&config.font);
}
/* Make bar config blocks without a configured font use the i3-wide font. */
Barconfig *current;
if (load_type != C_VALIDATE) {
TAILQ_FOREACH (current, &barconfigs, configs) {
if (current->font != NULL) {
continue;
}
current->font = sstrdup(config.font.pattern);
}
}
if (load_type == C_RELOAD) {
translate_keysyms();
grab_all_keys(conn);
regrab_all_buttons(conn);
gaps_reapply_workspace_assignments();
/* Redraw the currently visible decorations on reload, so that the
* possibly new drawing parameters changed. */
x_deco_recurse(croot);
xcb_flush(conn);
tree_render();
}
return result == 0;

View File

@ -163,15 +163,9 @@ i3_event_state_mask_t event_state_from_str(const char *str) {
return result;
}
static char *font_pattern;
CFGFUN(font, const char *font) {
config.font = load_font(font, true);
set_font(&config.font);
/* Save the font pattern for using it as bar font later on */
FREE(font_pattern);
font_pattern = sstrdup(font);
}
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
@ -186,6 +180,11 @@ static char *current_mode;
static bool current_mode_pango_markup;
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
if (current_mode == NULL) {
/* When using an invalid mode name, e.g. “default” */
return;
}
configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
}
@ -232,6 +231,102 @@ CFGFUN(for_window, const char *command) {
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
}
static void apply_gaps(gaps_t *gaps, gaps_mask_t mask, int value) {
if (gaps == NULL) {
return;
}
if (mask & GAPS_INNER) {
gaps->inner = value;
}
if (mask & GAPS_TOP) {
gaps->top = value;
}
if (mask & GAPS_RIGHT) {
gaps->right = value;
}
if (mask & GAPS_BOTTOM) {
gaps->bottom = value;
}
if (mask & GAPS_LEFT) {
gaps->left = value;
}
}
static void create_gaps_assignment(const char *workspace, const gaps_mask_t mask, const int pixels) {
if (mask == 0) {
return;
}
DLOG("Setting gaps for workspace %s", workspace);
bool found = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
if (strcasecmp(assignment->name, workspace) == 0) {
found = true;
break;
}
}
/* Assignment does not yet exist, let's create it. */
if (!found) {
assignment = scalloc(1, sizeof(struct Workspace_Assignment));
assignment->name = sstrdup(workspace);
assignment->output = NULL;
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
}
assignment->gaps_mask |= mask;
apply_gaps(&assignment->gaps, mask, pixels);
}
static gaps_mask_t gaps_scope_to_mask(const char *scope) {
if (!strcmp(scope, "inner")) {
return GAPS_INNER;
} else if (!strcmp(scope, "outer")) {
return GAPS_OUTER;
} else if (!strcmp(scope, "vertical")) {
return GAPS_VERTICAL;
} else if (!strcmp(scope, "horizontal")) {
return GAPS_HORIZONTAL;
} else if (!strcmp(scope, "top")) {
return GAPS_TOP;
} else if (!strcmp(scope, "right")) {
return GAPS_RIGHT;
} else if (!strcmp(scope, "bottom")) {
return GAPS_BOTTOM;
} else if (!strcmp(scope, "left")) {
return GAPS_LEFT;
}
ELOG("Invalid command, cannot process scope %s", scope);
return 0;
}
CFGFUN(gaps, const char *workspace, const char *scope, const long value) {
int pixels = logical_px(value);
gaps_mask_t mask = gaps_scope_to_mask(scope);
if (workspace == NULL) {
apply_gaps(&config.gaps, mask, pixels);
} else {
create_gaps_assignment(workspace, mask, pixels);
}
}
CFGFUN(smart_borders, const char *enable) {
if (!strcmp(enable, "no_gaps"))
config.smart_borders = SMART_BORDERS_NO_GAPS;
else
config.smart_borders = boolstr(enable) ? SMART_BORDERS_ON : SMART_BORDERS_OFF;
}
CFGFUN(smart_gaps, const char *enable) {
if (!strcmp(enable, "inverse_outer"))
config.smart_gaps = SMART_GAPS_INVERSE_OUTER;
else
config.smart_gaps = boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
}
CFGFUN(floating_minimum_size, const long width, const long height) {
config.floating_minimum_width = width;
config.floating_minimum_height = height;
@ -298,7 +393,9 @@ CFGFUN(default_border, const char *windowtype, const char *border, const long wi
}
CFGFUN(hide_edge_borders, const char *borders) {
if (strcmp(borders, "smart") == 0)
if (strcmp(borders, "smart_no_gaps") == 0)
config.hide_edge_borders = HEBM_SMART_NO_GAPS;
else if (strcmp(borders, "smart") == 0)
config.hide_edge_borders = HEBM_SMART;
else if (strcmp(borders, "vertical") == 0)
config.hide_edge_borders = HEBM_VERTICAL;
@ -314,6 +411,10 @@ CFGFUN(hide_edge_borders, const char *borders) {
config.hide_edge_borders = HEBM_NONE;
}
CFGFUN(empty_workspaces, const char *value) {
config.empty_workspaces = boolstr(value);
}
CFGFUN(focus_follows_mouse, const char *value) {
config.disable_focus_follows_mouse = !boolstr(value);
}
@ -418,9 +519,11 @@ CFGFUN(workspace, const char *workspace, const char *output) {
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
if (strcasecmp(assignment->name, workspace) == 0) {
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
workspace);
return;
if (assignment->output != NULL) {
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
workspace);
return;
}
}
}
@ -561,6 +664,20 @@ CFGFUN(ipc_kill_timeout, const long timeout_ms) {
ipc_set_kill_timeout(timeout_ms / 1000.0);
}
CFGFUN(tiling_drag, const char *value) {
if (strcmp(value, "modifier") == 0) {
config.tiling_drag = TILING_DRAG_MODIFIER;
} else if (strcmp(value, "titlebar") == 0) {
config.tiling_drag = TILING_DRAG_TITLEBAR;
} else if (strcmp(value, "modifier,titlebar") == 0 ||
strcmp(value, "titlebar,modifier") == 0) {
/* Switch the above to strtok() or similar if we ever grow more options */
config.tiling_drag = TILING_DRAG_MODIFIER_OR_TITLEBAR;
} else {
config.tiling_drag = TILING_DRAG_OFF;
}
}
/*******************************************************************************
* Bar configuration (i3bar)
******************************************************************************/
@ -600,6 +717,50 @@ CFGFUN(bar_verbose, const char *verbose) {
current_bar->verbose = boolstr(verbose);
}
CFGFUN(bar_height, const long height) {
current_bar->bar_height = (uint32_t)height;
}
static void dlog_padding(void) {
DLOG("padding now: x=%d, y=%d, w=%d, h=%d\n",
current_bar->padding.x,
current_bar->padding.y,
current_bar->padding.width,
current_bar->padding.height);
}
CFGFUN(bar_padding_one, const long all) {
current_bar->padding.x = (uint32_t)all;
current_bar->padding.y = (uint32_t)all;
current_bar->padding.width = (uint32_t)all;
current_bar->padding.height = (uint32_t)all;
dlog_padding();
}
CFGFUN(bar_padding_two, const long top_and_bottom, const long right_and_left) {
current_bar->padding.x = (uint32_t)right_and_left;
current_bar->padding.y = (uint32_t)top_and_bottom;
current_bar->padding.width = (uint32_t)right_and_left;
current_bar->padding.height = (uint32_t)top_and_bottom;
dlog_padding();
}
CFGFUN(bar_padding_three, const long top, const long right_and_left, const long bottom) {
current_bar->padding.x = (uint32_t)right_and_left;
current_bar->padding.y = (uint32_t)top;
current_bar->padding.width = (uint32_t)right_and_left;
current_bar->padding.height = (uint32_t)bottom;
dlog_padding();
}
CFGFUN(bar_padding_four, const long top, const long right, const long bottom, const long left) {
current_bar->padding.x = (uint32_t)left;
current_bar->padding.y = (uint32_t)top;
current_bar->padding.width = (uint32_t)right;
current_bar->padding.height = (uint32_t)bottom;
dlog_padding();
}
CFGFUN(bar_modifier, const char *modifiers) {
current_bar->modifier = modifiers ? event_state_from_str(modifiers) : XCB_NONE;
}
@ -716,6 +877,11 @@ CFGFUN(bar_status_command, const char *command) {
current_bar->status_command = sstrdup(command);
}
CFGFUN(bar_workspace_command, const char *command) {
FREE(current_bar->workspace_command);
current_bar->workspace_command = sstrdup(command);
}
CFGFUN(bar_binding_mode_indicator, const char *value) {
current_bar->hide_binding_mode_indicator = !boolstr(value);
}
@ -752,10 +918,6 @@ CFGFUN(bar_finish) {
config.number_barconfigs++;
/* If no font was explicitly set, we use the i3 font as default */
if (current_bar->font == NULL && font_pattern != NULL)
current_bar->font = sstrdup(font_pattern);
TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs);
/* Simply reset the pointer, but don't free the resources. */
current_bar = NULL;

View File

@ -919,7 +919,7 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
continuation = NULL;
}
strncpy(buf + strlen(buf), buffer, strlen(buffer) + 1);
strcpy(buf + strlen(buf), buffer);
/* Skip comments and empty lines. */
if (skip_line || comment) {
@ -1001,9 +1001,14 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
char *next;
for (next = bufcopy;
next < (bufcopy + stbuf.st_size) &&
(next = strcasestr(next, current->key)) != NULL;
next += strlen(current->key)) {
*next = '_';
(next = strcasestr(next, current->key)) != NULL;) {
/* We need to invalidate variables completely (otherwise we may count
* the same variable more than once, thus causing buffer overflow or
* allocation failure) with spaces (variable names cannot contain spaces) */
char *end = next + strlen(current->key);
while (next < end) {
*next++ = ' ';
}
extra_bytes += extra;
}
}
@ -1038,7 +1043,7 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
} else {
/* Copy until the next variable, then copy its value */
strncpy(destwalk, walk, distance);
strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
strcpy(destwalk + distance, nearest->value);
walk += distance + strlen(nearest->key);
destwalk += distance + strlen(nearest->value);
}

View File

@ -42,7 +42,8 @@ struct drag_x11_cb {
static bool threshold_exceeded(uint32_t x1, uint32_t y1,
uint32_t x2, uint32_t y2) {
const uint32_t threshold = 9;
/* The threshold is about the height of one window decoration. */
const uint32_t threshold = logical_px(15);
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold;
}

View File

@ -79,16 +79,22 @@ void floating_check_size(Con *floating_con, bool prefer_height) {
Rect floating_sane_max_dimensions;
Con *focused_con = con_descend_focused(floating_con);
DLOG("deco_rect.height = %d\n", focused_con->deco_rect.height);
Rect border_rect = con_border_style_rect(focused_con);
/* We have to do the opposite calculations that render_con() do
* to get the exact size we want. */
border_rect.width = -border_rect.width;
border_rect.width += 2 * focused_con->border_width;
border_rect.height = -border_rect.height;
/* undo x11 border */
border_rect.width += 2 * focused_con->border_width;
border_rect.height += 2 * focused_con->border_width;
if (con_border_style(focused_con) == BS_NORMAL) {
border_rect.height += render_deco_height();
}
DLOG("floating_check_size, want min width %d, min height %d, border extra: w=%d, h=%d\n",
floating_sane_min_width,
floating_sane_min_height,
border_rect.width,
border_rect.height);
i3Window *window = focused_con->window;
if (window != NULL) {
@ -319,9 +325,6 @@ bool floating_enable(Con *con, bool automatic) {
x_set_name(nc, name);
free(name);
/* find the height for the decorations */
int deco_height = render_deco_height();
DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
nc->rect = con->geometry;
@ -347,19 +350,15 @@ bool floating_enable(Con *con, bool automatic) {
con->floating = FLOATING_USER_ON;
/* 4: set the border style as specified with new_float */
if (automatic)
con->border_style = config.default_floating_border;
if (automatic) {
con->border_style = con->max_user_border_style = config.default_floating_border;
}
/* Add pixels for the decoration. */
Rect border_style_rect = con_border_style_rect(con);
Rect bsr = con_border_style_rect(con);
nc->rect.height -= border_style_rect.height;
nc->rect.width -= border_style_rect.width;
/* Add some more pixels for the title bar */
if (con_border_style(con) == BS_NORMAL) {
nc->rect.height += deco_height;
}
nc->rect.height -= bsr.height;
nc->rect.width -= bsr.width;
/* Honor the X11 border */
nc->rect.height += con->border_width * 2;
@ -405,14 +404,6 @@ bool floating_enable(Con *con, bool automatic) {
DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height);
/* 5: Subtract the deco_height in order to make the floating window appear
* at precisely the position it specified in its original geometry (which
* is what applications might remember). */
deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
nc->rect.y -= deco_height;
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
/* render the cons to get initial window_rect correct */
render_con(nc);
@ -698,6 +689,10 @@ void floating_resize_window(Con *con, const bool proportional,
const xcb_button_press_event_t *event) {
DLOG("floating_resize_window\n");
/* Push changes before resizing, so that the window gets raised now and not
* after the user releases the mouse button */
tree_render();
/* corner saves the nearest corner to the original click. It contains
* a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, ) */
border_t corner = 0;

169
src/gaps.c Normal file
View File

@ -0,0 +1,169 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* gaps.c: gaps logic: whether to display gaps at all, and how big
* they should be.
*
*/
#include "all.h"
/**
* Calculates the effective gap sizes for a container.
*/
gaps_t calculate_effective_gaps(Con *con) {
Con *workspace = con_get_workspace(con);
if (workspace == NULL)
return (gaps_t){0, 0, 0, 0, 0};
bool one_child = con_num_visible_children(workspace) <= 1 ||
(con_num_children(workspace) == 1 &&
(TAILQ_FIRST(&(workspace->nodes_head))->layout == L_TABBED ||
TAILQ_FIRST(&(workspace->nodes_head))->layout == L_STACKED));
if (config.smart_gaps == SMART_GAPS_ON && one_child)
return (gaps_t){0, 0, 0, 0, 0};
gaps_t gaps = {
.inner = (workspace->gaps.inner + config.gaps.inner),
.top = 0,
.right = 0,
.bottom = 0,
.left = 0};
if (config.smart_gaps != SMART_GAPS_INVERSE_OUTER || one_child) {
gaps.top = workspace->gaps.top + config.gaps.top;
gaps.right = workspace->gaps.right + config.gaps.right;
gaps.bottom = workspace->gaps.bottom + config.gaps.bottom;
gaps.left = workspace->gaps.left + config.gaps.left;
}
return gaps;
}
/*
* Decides whether the container should be inset.
*/
bool gaps_should_inset_con(Con *con, int children) {
/* No parent? None of the conditionals below can be true. */
if (con->parent == NULL) {
return false;
}
/* Floating split containers should never have gaps inside them. */
if (con_inside_floating(con)) {
return false;
}
const bool leaf_or_stacked_tabbed =
con_is_leaf(con) ||
(con->layout == L_STACKED || con->layout == L_TABBED);
/* Inset direct children of the workspace that are leaf containers or
stacked/tabbed containers. */
if (leaf_or_stacked_tabbed &&
con->parent->type == CT_WORKSPACE) {
return true;
}
/* Inset direct children of vertical or horizontal split containers at any
depth in the tree. Do not inset as soon as any parent is a stacked or
tabbed container, to avoid double insets. */
if (leaf_or_stacked_tabbed &&
!con_inside_stacked_or_tabbed(con) &&
con->parent->type == CT_CON &&
(con->parent->layout == L_SPLITH ||
con->parent->layout == L_SPLITV)) {
return true;
}
return false;
}
/*
* Returns whether the given container has an adjacent container in the
* specified direction. In other words, this returns true if and only if
* the container is not touching the edge of the screen in that direction.
*/
bool gaps_has_adjacent_container(Con *con, direction_t direction) {
Con *workspace = con_get_workspace(con);
Con *fullscreen = con_get_fullscreen_con(workspace, CF_GLOBAL);
if (fullscreen == NULL)
fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
/* If this container is fullscreen by itself, there's no adjacent container. */
if (con == fullscreen)
return false;
Con *first = con;
Con *second = NULL;
bool found_neighbor = resize_find_tiling_participants(&first, &second, direction, false);
if (!found_neighbor)
return false;
/* If we have an adjacent container and nothing is fullscreen, we consider it. */
if (fullscreen == NULL)
return true;
/* For fullscreen containers, only consider the adjacent container if it is also fullscreen. */
return con_has_parent(con, fullscreen) && con_has_parent(second, fullscreen);
}
/*
* Returns the configured gaps for this workspace based on the workspace name,
* number, and configured workspace gap assignments.
*/
gaps_t gaps_for_workspace(Con *ws) {
gaps_t gaps = (gaps_t){0, 0, 0, 0, 0};
gaps_mask_t mask = 0;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
if (strcmp(assignment->name, ws->name) == 0) {
gaps = assignment->gaps;
mask = assignment->gaps_mask;
break;
} else if (ws->num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == ws->num) {
gaps = assignment->gaps;
mask = assignment->gaps_mask;
}
}
if (mask == 0) {
return gaps;
}
if (mask & GAPS_INNER) {
gaps.inner -= config.gaps.inner;
}
if (mask & GAPS_TOP) {
gaps.top -= config.gaps.top;
}
if (mask & GAPS_RIGHT) {
gaps.right -= config.gaps.right;
}
if (mask & GAPS_BOTTOM) {
gaps.bottom -= config.gaps.bottom;
}
if (mask & GAPS_LEFT) {
gaps.left -= config.gaps.left;
}
return gaps;
}
/*
* Re-applies all workspace gap assignments to existing workspaces after
* reloading the configuration file.
*
*/
void gaps_reapply_workspace_assignments(void) {
Con *output, *workspace = NULL;
TAILQ_FOREACH (output, &(croot->nodes_head), nodes) {
Con *content = output_get_content(output);
TAILQ_FOREACH (workspace, &(content->nodes_head), nodes) {
DLOG("updating gap assignments for workspace %s\n", workspace->name);
workspace->gaps = gaps_for_workspace(workspace);
}
}
}

View File

@ -214,18 +214,30 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) {
return;
/* see over which rect the user is */
Con *current;
TAILQ_FOREACH_REVERSE (current, &(con->nodes_head), nodes_head, nodes) {
if (!rect_contains(current->deco_rect, event->event_x, event->event_y))
continue;
if (con->window != NULL) {
if (rect_contains(con->deco_rect, event->event_x, event->event_y)) {
/* We found the rect, lets see if this window is focused */
if (TAILQ_FIRST(&(con->parent->focus_head)) == con)
return;
/* We found the rect, lets see if this window is focused */
if (TAILQ_FIRST(&(con->focus_head)) == current)
con_focus(con);
x_push_changes(croot);
return;
}
} else {
Con *current;
TAILQ_FOREACH_REVERSE (current, &(con->nodes_head), nodes_head, nodes) {
if (!rect_contains(current->deco_rect, event->event_x, event->event_y))
continue;
con_focus(current);
x_push_changes(croot);
return;
/* We found the rect, lets see if this window is focused */
if (TAILQ_FIRST(&(con->focus_head)) == current)
return;
con_focus(current);
x_push_changes(croot);
return;
}
}
}
@ -318,15 +330,9 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
/* find the height for the decorations */
int deco_height = con->deco_rect.height;
/* we actually need to apply the size/position changes to the *parent*
* container */
Rect bsr = con_border_style_rect(con);
if (con->border_style == BS_NORMAL) {
bsr.y += deco_height;
bsr.height -= deco_height;
}
Con *floatingcon = con->parent;
Rect newrect = floatingcon->rect;
@ -1187,9 +1193,9 @@ static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
*/
static bool handle_motif_hints_change(Con *con, xcb_get_property_reply_t *prop) {
border_style_t motif_border_style;
window_update_motif_hints(con->window, prop, &motif_border_style);
bool has_mwm_hints = window_update_motif_hints(con->window, prop, &motif_border_style);
if (motif_border_style != con->border_style && motif_border_style != BS_NORMAL) {
if (has_mwm_hints && motif_border_style != con->border_style) {
DLOG("Update border style of con %p to %d\n", con, motif_border_style);
con_set_border_style(con, motif_border_style, con->current_border_width);

View File

@ -245,6 +245,28 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
y(map_close);
}
static void dump_gaps(yajl_gen gen, const char *name, gaps_t gaps) {
ystr(name);
y(map_open);
ystr("inner");
y(integer, gaps.inner);
// TODO: the i3ipc Python modules recognize gaps, but only inner/outer
// This is currently here to preserve compatibility with that
ystr("outer");
y(integer, gaps.top);
ystr("top");
y(integer, gaps.top);
ystr("right");
y(integer, gaps.right);
ystr("bottom");
y(integer, gaps.bottom);
ystr("left");
y(integer, gaps.left);
y(map_close);
}
static void dump_event_state_mask(yajl_gen gen, Binding *bind) {
y(array_open);
for (int i = 0; i < 20; i++) {
@ -481,7 +503,15 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
y(integer, con->current_border_width);
dump_rect(gen, "rect", con->rect);
dump_rect(gen, "deco_rect", con->deco_rect);
if (con_draw_decoration_into_frame(con)) {
Rect simulated_deco_rect = con->deco_rect;
simulated_deco_rect.x = con->rect.x - con->parent->rect.x;
simulated_deco_rect.y = con->rect.y - con->parent->rect.y;
dump_rect(gen, "deco_rect", simulated_deco_rect);
dump_rect(gen, "actual_deco_rect", con->deco_rect);
} else {
dump_rect(gen, "deco_rect", con->deco_rect);
}
dump_rect(gen, "window_rect", con->window_rect);
dump_rect(gen, "geometry", con->geometry);
@ -504,6 +534,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
if (con->type == CT_WORKSPACE) {
ystr("num");
y(integer, con->num);
dump_gaps(gen, "gaps", con->gaps);
}
ystr("window");
@ -707,7 +739,7 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
static char *canonicalize_output_name(char *name) {
/* Do not canonicalize special output names. */
if (strcasecmp(name, "primary") == 0) {
if (strcasecmp(name, "primary") == 0 || strcasecmp(name, "nonprimary") == 0) {
return name;
}
Output *output = get_output_by_name(name, false);
@ -795,8 +827,16 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
ystr("top");
YSTR_IF_SET(status_command);
YSTR_IF_SET(workspace_command);
YSTR_IF_SET(font);
if (config->bar_height) {
ystr("bar_height");
y(integer, config->bar_height);
}
dump_rect(gen, "padding", config->padding);
if (config->separator_symbol) {
ystr("separator_symbol");
ystr(config->separator_symbol);
@ -1639,7 +1679,7 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) {
/*
* For the binding events, we send the serialized binding struct.
*/
void ipc_send_binding_event(const char *event_type, Binding *bind) {
void ipc_send_binding_event(const char *event_type, Binding *bind, const char *modename) {
DLOG("Issue IPC binding %s event (sym = %s, code = %d)\n", event_type, bind->symbol, bind->keycode);
setlocale(LC_NUMERIC, "C");
@ -1651,6 +1691,13 @@ void ipc_send_binding_event(const char *event_type, Binding *bind) {
ystr("change");
ystr(event_type);
ystr("mode");
if (modename == NULL) {
ystr("default");
} else {
ystr(modename);
}
ystr("binding");
dump_binding(gen, bind);

View File

@ -20,8 +20,10 @@ static char *last_key;
static int incomplete;
static Con *json_node;
static Con *to_focus;
static bool parsing_gaps;
static bool parsing_swallows;
static bool parsing_rect;
static bool parsing_actual_deco_rect;
static bool parsing_deco_rect;
static bool parsing_window_rect;
static bool parsing_geometry;
@ -60,7 +62,12 @@ static int json_start_map(void *ctx) {
TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches);
swallow_is_empty = true;
} else {
if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
if (!parsing_rect &&
!parsing_actual_deco_rect &&
!parsing_deco_rect &&
!parsing_window_rect &&
!parsing_geometry &&
!parsing_gaps) {
if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
DLOG("New floating_node\n");
Con *ws = con_get_workspace(json_node);
@ -84,7 +91,13 @@ static int json_start_map(void *ctx) {
static int json_end_map(void *ctx) {
LOG("end of map\n");
if (!parsing_swallows && !parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
if (!parsing_swallows &&
!parsing_rect &&
!parsing_actual_deco_rect &&
!parsing_deco_rect &&
!parsing_window_rect &&
!parsing_geometry &&
!parsing_gaps) {
/* Set a few default values to simplify manually crafted layout files. */
if (json_node->layout == L_DEFAULT) {
DLOG("Setting layout = L_SPLITH\n");
@ -192,7 +205,9 @@ static int json_end_map(void *ctx) {
return 0;
}
parsing_gaps = false;
parsing_rect = false;
parsing_actual_deco_rect = false;
parsing_deco_rect = false;
parsing_window_rect = false;
parsing_geometry = false;
@ -245,9 +260,15 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
if (strcasecmp(last_key, "swallows") == 0)
parsing_swallows = true;
if (strcasecmp(last_key, "gaps") == 0)
parsing_gaps = true;
if (strcasecmp(last_key, "rect") == 0)
parsing_rect = true;
if (strcasecmp(last_key, "actual_deco_rect") == 0)
parsing_actual_deco_rect = true;
if (strcasecmp(last_key, "deco_rect") == 0)
parsing_deco_rect = true;
@ -506,6 +527,18 @@ static int json_int(void *ctx, long long val) {
swallow_is_empty = false;
}
}
if (parsing_gaps) {
if (strcasecmp(last_key, "inner") == 0)
json_node->gaps.inner = val;
else if (strcasecmp(last_key, "top") == 0)
json_node->gaps.top = val;
else if (strcasecmp(last_key, "right") == 0)
json_node->gaps.right = val;
else if (strcasecmp(last_key, "bottom") == 0)
json_node->gaps.bottom = val;
else if (strcasecmp(last_key, "left") == 0)
json_node->gaps.left = val;
}
return 1;
}
@ -653,9 +686,11 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
yajl_config(hand, yajl_dont_validate_strings, true);
json_node = con;
to_focus = NULL;
parsing_gaps = false;
incomplete = 0;
parsing_swallows = false;
parsing_rect = false;
parsing_actual_deco_rect = false;
parsing_deco_rect = false;
parsing_window_rect = false;
parsing_geometry = false;

View File

@ -137,7 +137,11 @@ void open_logbuffer(void) {
* For 512 MiB of RAM this will lead to a 5 MiB log buffer.
* At the moment (2011-12-10), no testcase leads to an i3 log
* of more than ~ 600 KiB. */
logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
logbuffer_size = shmlog_size;
if (physical_mem_bytes * 0.01 < (long long)shmlog_size) {
logbuffer_size = physical_mem_bytes * 0.01;
}
#if defined(__FreeBSD__)
sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid());
#else

View File

@ -1002,10 +1002,11 @@ int main(int argc, char *argv[]) {
char *log_stream_socket_path = get_process_filename("log-stream-socket");
int log_socket = create_socket(log_stream_socket_path, &current_log_stream_socket_path);
free(log_stream_socket_path);
struct ev_io *log_io = NULL;
if (log_socket == -1) {
ELOG("Could not create the log socket, i3-dump-log -f will not work\n");
} else {
struct ev_io *log_io = scalloc(1, sizeof(struct ev_io));
log_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(log_io, log_new_client, log_socket, EV_READ);
ev_io_start(main_loop, log_io);
}
@ -1013,12 +1014,13 @@ int main(int argc, char *argv[]) {
/* Also handle the UNIX domain sockets passed via socket
* activation. The parameter 0 means "do not remove the
* environment variables", we need to be able to reexec. */
struct ev_io *socket_ipc_io = NULL;
listen_fds = sd_listen_fds(0);
if (listen_fds < 0)
if (listen_fds < 0) {
ELOG("socket activation: Error in sd_listen_fds\n");
else if (listen_fds == 0)
} else if (listen_fds == 0) {
DLOG("socket activation: no sockets passed\n");
else {
} else {
int flags;
for (int fd = SD_LISTEN_FDS_START;
fd < (SD_LISTEN_FDS_START + listen_fds);
@ -1033,9 +1035,9 @@ int main(int argc, char *argv[]) {
ELOG("Could not disable FD_CLOEXEC on fd %d\n", fd);
}
struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
ev_io_start(main_loop, ipc_io);
socket_ipc_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(socket_ipc_io, ipc_new_client, fd, EV_READ);
ev_io_start(main_loop, socket_ipc_io);
}
}
@ -1197,5 +1199,16 @@ int main(int argc, char *argv[]) {
atexit(i3_exit);
sd_notify(1, "READY=1");
if (config.empty_workspaces) {
workspace_init();
}
ev_loop(main_loop, 0);
/* Free these heap allocations just to satisfy LeakSanitizer. */
FREE(ipc_io);
FREE(socket_ipc_io);
FREE(log_io);
FREE(xcb_watcher);
}

View File

@ -214,8 +214,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL));
bool urgency_hint;
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
border_style_t motif_border_style = BS_NORMAL;
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
border_style_t motif_border_style;
bool has_mwm_hints = window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
@ -511,7 +511,14 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
if (nc->geometry.width == 0)
nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height};
if (motif_border_style != BS_NORMAL) {
if (want_floating) {
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
if (floating_enable(nc, true)) {
nc->floating = FLOATING_AUTO_ON;
}
}
if (has_mwm_hints) {
DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
if (want_floating) {
con_set_border_style(nc, motif_border_style, config.default_floating_border_width);
@ -520,17 +527,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
}
}
if (want_floating) {
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
/* automatically set the border to the default value if a motif border
* was not specified */
bool automatic_border = (motif_border_style == BS_NORMAL);
if (floating_enable(nc, automatic_border)) {
nc->floating = FLOATING_AUTO_ON;
}
}
/* explicitly set the border width to the default */
if (nc->current_border_width == -1) {
nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width);

View File

@ -48,8 +48,10 @@ static Output *get_output_by_id(xcb_randr_output_t id) {
*
*/
Output *get_output_by_name(const char *name, const bool require_active) {
const bool get_primary = (strcasecmp("primary", name) == 0);
const bool get_non_primary = (strcasecmp("nonprimary", name) == 0);
Output *output;
bool get_primary = (strcasecmp("primary", name) == 0);
TAILQ_FOREACH (output, &outputs, outputs) {
if (require_active && !output->active) {
continue;
@ -57,6 +59,9 @@ Output *get_output_by_name(const char *name, const bool require_active) {
if (output->primary && get_primary) {
return output;
}
if (!output->primary && get_non_primary) {
return output;
}
struct output_name *output_name;
SLIST_FOREACH (output_name, &output->names_head, names) {
if (strcasecmp(output_name->name, name) == 0) {

View File

@ -49,6 +49,41 @@ void render_con(Con *con) {
DLOG("Rendering node %p / %s / layout %d / children %d\n", con, con->name,
con->layout, params.children);
if (con->type == CT_WORKSPACE) {
gaps_t gaps = calculate_effective_gaps(con);
Rect inset = (Rect){
gaps.left,
gaps.top,
-(gaps.left + gaps.right),
-(gaps.top + gaps.bottom),
};
con->rect = rect_add(con->rect, inset);
params.rect = rect_add(params.rect, inset);
params.x += gaps.left;
params.y += gaps.top;
}
if (gaps_should_inset_con(con, params.children)) {
gaps_t gaps = calculate_effective_gaps(con);
Rect inset = (Rect){
gaps_has_adjacent_container(con, D_LEFT) ? gaps.inner / 2 : gaps.inner,
gaps_has_adjacent_container(con, D_UP) ? gaps.inner / 2 : gaps.inner,
gaps_has_adjacent_container(con, D_RIGHT) ? -(gaps.inner / 2) : -gaps.inner,
gaps_has_adjacent_container(con, D_DOWN) ? -(gaps.inner / 2) : -gaps.inner,
};
inset.width -= inset.x;
inset.height -= inset.y;
if (con->fullscreen_mode == CF_NONE) {
params.rect = rect_add(params.rect, inset);
con->rect = rect_add(con->rect, inset);
}
inset.height = 0;
params.x = con->rect.x;
params.y = con->rect.y;
}
int i = 0;
con->mapped = true;
@ -56,17 +91,27 @@ void render_con(Con *con) {
if (con->window) {
/* depending on the border style, the rect of the child window
* needs to be smaller */
Rect *inset = &(con->window_rect);
*inset = (Rect){0, 0, con->rect.width, con->rect.height};
Rect inset = (Rect){
.x = 0,
.y = 0,
.width = con->rect.width,
.height = con->rect.height,
};
if (con->fullscreen_mode == CF_NONE) {
*inset = rect_add(*inset, con_border_style_rect(con));
DLOG("deco_rect.height = %d\n", con->deco_rect.height);
Rect bsr = con_border_style_rect(con);
DLOG("bsr at %dx%d with size %dx%d\n",
bsr.x, bsr.y, bsr.width, bsr.height);
inset = rect_add(inset, bsr);
}
/* Obey x11 border */
inset->width -= (2 * con->border_width);
inset->height -= (2 * con->border_width);
inset.width -= (2 * con->border_width);
inset.height -= (2 * con->border_width);
*inset = rect_sanitize_dimensions(*inset);
inset = rect_sanitize_dimensions(inset);
con->window_rect = inset;
/* NB: We used to respect resize increment size hints for tiling
* windows up until commit 0db93d9 here. However, since all terminal
@ -74,7 +119,8 @@ void render_con(Con *con) {
* can (by providing their fake-transparency or background color), this
* code was removed. See also https://bugs.i3wm.org/540 */
DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height);
DLOG("child will be at %dx%d with size %dx%d\n",
inset.x, inset.y, inset.width, inset.height);
}
/* Check for fullscreen nodes */
@ -131,6 +177,18 @@ void render_con(Con *con) {
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
x_raise_con(child);
render_con(child);
/* render_con_split() sets the deco_rect width based on the rect
* width, but the render_con() call updates the rect width by
* applying gaps, so we need to update deco_rect. */
if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
if (con_is_leaf(child)) {
if (child->border_style == BS_NORMAL) {
child->deco_rect.width = child->rect.width;
}
}
}
i++;
}
@ -353,11 +411,8 @@ static void render_con_split(Con *con, Con *child, render_params *p, int i) {
if (con_is_leaf(child)) {
if (child->border_style == BS_NORMAL) {
/* TODO: make a function for relative coords? */
child->deco_rect.x = child->rect.x - con->rect.x;
child->deco_rect.y = child->rect.y - con->rect.y;
child->rect.y += p->deco_height;
child->rect.height -= p->deco_height;
child->deco_rect.x = 0;
child->deco_rect.y = 0;
child->deco_rect.width = child->rect.width;
child->deco_rect.height = p->deco_height;

View File

@ -173,6 +173,8 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
bool use_threshold) {
Con *output = con_get_output(first);
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
DLOG("first = %p / %s\n", first, first->name);
DLOG("second = %p / %s\n", second, second->name);
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
xcb_flush(conn);
@ -197,14 +199,31 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
Rect helprect;
helprect.x = second->rect.x;
helprect.y = second->rect.y;
/* Resizes might happen between a split container and a leaf
* container. Because gaps happen *within* a split container, we need to
* work with (any) leaf window inside the split, so descend focused. */
Con *ffirst = con_descend_focused(first);
Con *fsecond = con_descend_focused(second);
if (orientation == HORIZ) {
helprect.width = logical_px(2);
helprect.height = second->rect.height;
initial_position = second->rect.x;
const uint32_t ffirst_right = ffirst->rect.x + ffirst->rect.width;
const uint32_t gap = (fsecond->rect.x - ffirst_right);
const uint32_t middle = fsecond->rect.x - (gap / 2);
DLOG("ffirst->rect = {.x = %u, .width = %u}\n", ffirst->rect.x, ffirst->rect.width);
DLOG("fsecond->rect = {.x = %u, .width = %u}\n", fsecond->rect.x, fsecond->rect.width);
DLOG("gap = %u, middle = %u\n", gap, middle);
initial_position = middle;
} else {
helprect.width = second->rect.width;
helprect.height = logical_px(2);
initial_position = second->rect.y;
const uint32_t ffirst_bottom = ffirst->rect.y + ffirst->rect.height;
const uint32_t gap = (fsecond->rect.y - ffirst_bottom);
const uint32_t middle = fsecond->rect.y - (gap / 2);
DLOG("ffirst->rect = {.y = %u, .height = %u}\n", ffirst->rect.y, ffirst->rect.height);
DLOG("fsecond->rect = {.y = %u, .height = %u}\n", fsecond->rect.y, fsecond->rect.height);
DLOG("gap = %u, middle = %u\n", gap, middle);
initial_position = middle;
}
mask = XCB_CW_BACK_PIXEL;
@ -220,10 +239,10 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
xcb_map_window(conn, helpwin);
if (orientation == HORIZ) {
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
second->rect.x, event->root_y);
initial_position, event->root_y);
} else {
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
event->root_x, second->rect.y);
event->root_x, initial_position);
}
}

View File

@ -340,6 +340,11 @@ int sd_notify(int unset_environment, const char *state) {
goto finish;
}
if (strlen(e) > sizeof(sockaddr.un.sun_path)) {
r = -EINVAL;
goto finish;
}
if ((fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0) {
r = -errno;
goto finish;
@ -347,7 +352,7 @@ int sd_notify(int unset_environment, const char *state) {
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sa.sa_family = AF_UNIX;
strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path) - 1);
if (sockaddr.un.sun_path[0] == '@')
sockaddr.un.sun_path[0] = 0;

View File

@ -10,17 +10,60 @@
#include "all.h"
static xcb_window_t create_drop_indicator(Rect rect);
static bool is_tiling_drop_target(Con *con) {
if (!con_has_managed_window(con) ||
con_is_floating(con) ||
con_is_hidden(con)) {
return false;
}
Con *ws = con_get_workspace(con);
if (con_is_internal(ws)) {
/* Skip containers on i3-internal containers like the scratchpad, which are
technically visible on their pseudo-output. */
return false;
}
if (!workspace_is_visible(ws)) {
return false;
}
Con *fs = con_get_fullscreen_covering_ws(ws);
if (fs != NULL && fs != con) {
/* Workspace is visible, but con is not visible because some other
container is in fullscreen. */
return false;
}
return true;
}
/*
* Includes decoration (container title) to the container's rect. This way we
* can find the correct drop target if the mouse is on a container's
* decoration.
* Returns whether there currently are any drop targets.
* Used to only initiate a drag when there is something to drop onto.
*
*/
static Rect con_rect_plus_deco_height(Con *con) {
Rect rect = con->rect;
rect.height += con->deco_rect.height;
rect.y -= con->deco_rect.height;
return rect;
bool has_drop_targets(void) {
int drop_targets = 0;
Con *con;
TAILQ_FOREACH (con, &all_cons, all_cons) {
if (!is_tiling_drop_target(con)) {
continue;
}
drop_targets++;
}
/* In addition to tiling containers themselves, an visible but empty
* workspace (in a multi-monitor scenario) also is a drop target. */
Con *output;
TAILQ_FOREACH (output, &(croot->focus_head), focused) {
if (con_is_internal(output)) {
continue;
}
Con *visible_ws = NULL;
GREP_FIRST(visible_ws, output_get_content(output), workspace_is_visible(child));
if (visible_ws != NULL && con_num_children(visible_ws) == 0) {
drop_targets++;
}
}
return drop_targets > 1;
}
/*
@ -30,19 +73,14 @@ static Rect con_rect_plus_deco_height(Con *con) {
static Con *find_drop_target(uint32_t x, uint32_t y) {
Con *con;
TAILQ_FOREACH (con, &all_cons, all_cons) {
Rect rect = con_rect_plus_deco_height(con);
if (rect_contains(rect, x, y) &&
con_has_managed_window(con) &&
!con_is_floating(con) &&
!con_is_hidden(con)) {
Con *ws = con_get_workspace(con);
if (!workspace_is_visible(ws)) {
continue;
}
Con *fs = con_get_fullscreen_covering_ws(ws);
return fs ? fs : con;
Rect rect = con->rect;
if (!rect_contains(rect, x, y) ||
!is_tiling_drop_target(con)) {
continue;
}
Con *ws = con_get_workspace(con);
Con *fs = con_get_fullscreen_covering_ws(ws);
return fs ? fs : con;
}
/* Couldn't find leaf container, get a workspace. */
@ -131,7 +169,7 @@ DRAGGING_CB(drag_callback) {
return;
}
Rect rect = con_rect_plus_deco_height(target);
Rect rect = target->rect;
direction_t direction = 0;
drop_type_t drop_type = DT_CENTER;
@ -263,7 +301,7 @@ static xcb_window_t create_drop_indicator(Rect rect) {
* Initiates a mouse drag operation on a tiled window.
*
*/
void tiling_drag(Con *con, xcb_button_press_event_t *event) {
void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold) {
DLOG("Start dragging tiled container: con = %p\n", con);
bool set_focus = (con == focused);
bool set_fs = con->fullscreen_mode != CF_NONE;
@ -279,7 +317,7 @@ void tiling_drag(Con *con, xcb_button_press_event_t *event) {
xcb_window_t indicator = 0;
const struct callback_params params = {&indicator, &target, &direction, &drop_type};
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP, XCURSOR_CURSOR_MOVE, drag_callback, &params);
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_callback, &params);
/* Dragging is done. We don't need the indicator window any more. */
xcb_destroy_window(conn, indicator);
@ -374,6 +412,7 @@ void tiling_drag(Con *con, xcb_button_press_event_t *event) {
con_enable_fullscreen(con, CF_OUTPUT);
}
if (set_focus) {
workspace_show(con_get_workspace(con));
con_focus(con);
}
tree_render();

View File

@ -470,7 +470,11 @@ static Con *get_tree_next_workspace(Con *con, direction_t direction) {
return NULL;
}
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
// Use the center of the container instead of the left/top edges, to make
// this work with negative gaps. See https://github.com/i3/i3/issues/5293
const uint32_t x = con->rect.x + (con->rect.width / 2);
const uint32_t y = con->rect.y + (con->rect.height / 2);
Output *current_output = get_output_containing(x, y);
if (!current_output) {
return NULL;
}

View File

@ -403,6 +403,49 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
free(prop);
}
/* See `man VendorShell' for more info, `XmNmwmDecorations' section:
* https://linux.die.net/man/3/vendorshell
* The following constants are adapted from <Xm/MwmUtil.h>.
*/
#define MWM_HINTS_FLAGS_FIELD 0
#define MWM_HINTS_DECORATIONS_FIELD 2
#define MWM_HINTS_DECORATIONS (1 << 1)
#define MWM_DECOR_ALL (1 << 0)
#define MWM_DECOR_BORDER (1 << 1)
#define MWM_DECOR_TITLE (1 << 3)
static border_style_t border_style_from_motif_value(uint32_t value) {
if (value & MWM_DECOR_ALL) {
/* If this value is set, all other flags set are exclusive:
* MWM_DECOR_ALL
* All decorations except those specified by other flag bits that are set
*
* We support these cases:
* - No exceptions -> BS_NORMAL
* - Title and no border (ignored) -> BS_NORMAL
* - No title and no border -> BS_NONE
* - No title and border -> BS_PIXEL
* */
if (value & MWM_DECOR_TITLE) {
if (value & MWM_DECOR_BORDER) {
return BS_NONE;
}
return BS_PIXEL;
}
return BS_NORMAL;
} else if (value & MWM_DECOR_TITLE) {
return BS_NORMAL;
} else if (value & MWM_DECOR_BORDER) {
return BS_PIXEL;
} else {
return BS_NONE;
}
}
/*
* Updates the MOTIF_WM_HINTS. The container's border style should be set to
* `motif_border_style' if border style is not BS_NORMAL.
@ -415,27 +458,15 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
*
*/
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
/* This implementation simply mirrors Gnome's Metacity. Official
* documentation of this hint is nowhere to be found.
* For more information see:
* https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html
* https://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations
*/
#define MWM_HINTS_FLAGS_FIELD 0
#define MWM_HINTS_DECORATIONS_FIELD 2
bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
if (prop == NULL) {
return false;
}
assert(motif_border_style != NULL);
#define MWM_HINTS_DECORATIONS (1 << 1)
#define MWM_DECOR_ALL (1 << 0)
#define MWM_DECOR_BORDER (1 << 1)
#define MWM_DECOR_TITLE (1 << 3)
if (motif_border_style != NULL)
*motif_border_style = BS_NORMAL;
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
if (xcb_get_property_value_length(prop) == 0) {
FREE(prop);
return;
return false;
}
/* The property consists of an array of 5 uint32_t's. The first value is a
@ -449,18 +480,14 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
* i.e. returns 32-bit data fields. */
uint32_t *motif_hints = (uint32_t *)xcb_get_property_value(prop);
if (motif_border_style != NULL &&
motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) {
if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_ALL ||
motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_TITLE)
*motif_border_style = BS_NORMAL;
else if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_BORDER)
*motif_border_style = BS_PIXEL;
else
*motif_border_style = BS_NONE;
if (motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) {
*motif_border_style = border_style_from_motif_value(motif_hints[MWM_HINTS_DECORATIONS_FIELD]);
FREE(prop);
return true;
}
FREE(prop);
return false;
}
#undef MWM_HINTS_FLAGS_FIELD
#undef MWM_HINTS_DECORATIONS_FIELD
@ -468,7 +495,6 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
#undef MWM_DECOR_ALL
#undef MWM_DECOR_BORDER
#undef MWM_DECOR_TITLE
}
/*
* Updates the WM_CLIENT_MACHINE

View File

@ -85,6 +85,10 @@ Con *get_assigned_output(const char *name, long parsed_num) {
Con *output = NULL;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
if (assignment->output == NULL) {
continue;
}
if (name && strcmp(assignment->name, name) == 0) {
DLOG("Found workspace name=\"%s\" assignment to output \"%s\"\n",
name, assignment->output);
@ -156,6 +160,7 @@ Con *workspace_get(const char *num) {
workspace->workspace_layout = config.default_layout;
workspace->num = parsed_num;
workspace->type = CT_WORKSPACE;
workspace->gaps = gaps_for_workspace(workspace);
con_attach(workspace, output_get_content(output), false);
_workspace_apply_default_orientation(workspace);
@ -281,6 +286,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
ws->num = c;
sasprintf(&(ws->name), "%d", c);
}
con_attach(ws, content, false);
char *name;
@ -288,6 +294,8 @@ Con *create_workspace_on_output(Output *output, Con *content) {
x_set_name(ws, name);
free(name);
ws->gaps = gaps_for_workspace(ws);
ws->fullscreen_mode = CF_OUTPUT;
ws->workspace_layout = config.default_layout;
@ -506,8 +514,9 @@ void workspace_show(Con *workspace) {
* client, which will clear the urgency flag too early. Also, there is no
* way for con_focus() to know about when to clear urgency immediately and
* when to defer it. */
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
/* check if this workspace is currently visible */
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))
&& !config.empty_workspaces) {
if (!workspace_is_visible(old)) {
LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
@ -520,7 +529,6 @@ void workspace_show(Con *workspace) {
y(free);
/* Avoid calling output_push_sticky_windows later with a freed container. */
if (old == old_focus) {
old_focus = NULL;
}
@ -1065,3 +1073,19 @@ void workspace_move_to_output(Con *ws, Output *output) {
break;
}
}
void workspace_init(void)
{
int i = 0;
char *c = binding_workspace_names[0];
while (c != NULL) {
DLOG("bwn: %s\n", c);
workspace_get(c);
i++;
c = binding_workspace_names[i];
}
//workspace_show_by_name(binding_workspace_names[0]);
}

62
src/x.c
View File

@ -355,29 +355,27 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) {
free(event);
}
static void x_draw_title_border(Con *con, struct deco_render_params *p) {
assert(con->parent != NULL);
static void x_draw_title_border(Con *con, struct deco_render_params *p, surface_t *dest_surface) {
Rect *dr = &(con->deco_rect);
/* Left */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
draw_util_rectangle(dest_surface, p->color->border,
dr->x, dr->y, 1, dr->height);
/* Right */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
draw_util_rectangle(dest_surface, p->color->border,
dr->x + dr->width - 1, dr->y, 1, dr->height);
/* Top */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
draw_util_rectangle(dest_surface, p->color->border,
dr->x, dr->y, dr->width, 1);
/* Bottom */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
draw_util_rectangle(dest_surface, p->color->border,
dr->x, dr->y + dr->height - 1, dr->width, 1);
}
static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p) {
static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p, surface_t *dest_surface) {
assert(con->parent != NULL);
Rect *dr = &(con->deco_rect);
@ -389,7 +387,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
/* We actually only redraw the far right two pixels as that is the
* distance we keep from the edge (not the entire border width).
* Redrawing the entire border would cause text to be cut off. */
draw_util_rectangle(&(con->parent->frame_buffer), p->color->background,
draw_util_rectangle(dest_surface, p->color->background,
dr->x + dr->width - 2 * logical_px(1),
dr->y,
2 * logical_px(1),
@ -397,7 +395,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
}
/* Redraw the border. */
x_draw_title_border(con, p);
x_draw_title_border(con, p, dest_surface);
}
/*
@ -593,16 +591,24 @@ void x_draw_decoration(Con *con) {
}
}
surface_t *dest_surface = &(parent->frame_buffer);
if (con_draw_decoration_into_frame(con)) {
DLOG("using con->frame_buffer (for con->name=%s) as dest_surface\n", con->name);
dest_surface = &(con->frame_buffer);
} else {
DLOG("sticking to parent->frame_buffer = %p\n", dest_surface);
}
DLOG("dest_surface %p is %d x %d (id=0x%08x)\n", dest_surface, dest_surface->width, dest_surface->height, dest_surface->id);
/* If the parent hasn't been set up yet, skip the decoration rendering
* for now. */
if (parent->frame_buffer.id == XCB_NONE)
if (dest_surface->id == XCB_NONE)
goto copy_pixmaps;
/* For the first child, we clear the parent pixmap to ensure there's no
* garbage left on there. This is important to avoid tearing when using
* transparency. */
if (con == TAILQ_FIRST(&(con->parent->nodes_head))) {
draw_util_clear_surface(&(con->parent->frame_buffer), COLOR_TRANSPARENT);
FREE(con->parent->deco_render_params);
}
@ -612,11 +618,13 @@ void x_draw_decoration(Con *con) {
goto copy_pixmaps;
/* 4: paint the bar */
draw_util_rectangle(&(parent->frame_buffer), p->color->background,
DLOG("con->deco_rect = (x=%d, y=%d, w=%d, h=%d) for con->name=%s\n",
con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height, con->name);
draw_util_rectangle(dest_surface, p->color->background,
con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height);
/* 5: draw title border */
x_draw_title_border(con, p);
x_draw_title_border(con, p, dest_surface);
/* 6: draw the icon and title */
int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
@ -651,7 +659,7 @@ void x_draw_decoration(Con *con) {
? title_padding
: deco_width - mark_width - title_padding;
draw_util_text(mark, &(parent->frame_buffer),
draw_util_text(mark, dest_surface,
p->color->text, p->color->background,
con->deco_rect.x + mark_offset_x,
con->deco_rect.y + text_offset_y, mark_width);
@ -723,9 +731,12 @@ void x_draw_decoration(Con *con) {
/* Make sure the icon does not escape title boundaries */
icon_offset_x = min(deco_width - icon_size - icon_padding - title_padding, title_offset_x + predict_text_width(title) + icon_padding);
break;
default:
ELOG("BUG: invalid config.title_align value %d\n", config.title_align);
return;
}
draw_util_text(title, &(parent->frame_buffer),
draw_util_text(title, dest_surface,
p->color->text, p->color->background,
con->deco_rect.x + title_offset_x,
con->deco_rect.y + text_offset_y,
@ -733,7 +744,7 @@ void x_draw_decoration(Con *con) {
if (has_icon) {
draw_util_image(
win->icon,
&(parent->frame_buffer),
dest_surface,
con->deco_rect.x + icon_offset_x,
con->deco_rect.y + logical_px(1),
icon_size,
@ -744,7 +755,7 @@ void x_draw_decoration(Con *con) {
I3STRING_FREE(title);
}
x_draw_decoration_after_title(con, p);
x_draw_decoration_after_title(con, p, dest_surface);
copy_pixmaps:
draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
}
@ -891,7 +902,7 @@ void x_push_node(Con *con) {
FREE(state->name);
}
if (con->window == NULL) {
if (con->window == NULL && (con->layout == L_STACKED || con->layout == L_TABBED)) {
/* Calculate the height of all window decorations which will be drawn on to
* this frame. */
uint32_t max_y = 0, max_height = 0;
@ -905,6 +916,9 @@ void x_push_node(Con *con) {
rect.height = max_y + max_height;
if (rect.height == 0)
con->mapped = false;
} else if (con->window == NULL) {
/* not a stacked or tabbed split container */
con->mapped = false;
}
bool need_reshape = false;
@ -949,10 +963,11 @@ void x_push_node(Con *con) {
/* The pixmap of a borderless leaf container will not be used except
* for the titlebar in a stack or tabs (issue #1013). */
bool is_pixmap_needed = (con->border_style != BS_NONE ||
!con_is_leaf(con) ||
con->parent->layout == L_STACKED ||
con->parent->layout == L_TABBED);
bool is_pixmap_needed = ((con_is_leaf(con) && con_border_style(con) != BS_NONE) ||
con->layout == L_STACKED ||
con->layout == L_TABBED);
DLOG("Con %p (layout %d), is_pixmap_needed = %s, rect.height = %d\n",
con, con->layout, is_pixmap_needed ? "yes" : "no", con->rect.height);
/* The root con and output cons will never require a pixmap. In particular for the
* __i3 output, this will likely not work anyway because it might be ridiculously
@ -999,6 +1014,7 @@ void x_push_node(Con *con) {
int width = MAX((int32_t)rect.width, 1);
int height = MAX((int32_t)rect.height, 1);
DLOG("creating %d x %d pixmap for con %p (con->frame_buffer.id = (pixmap_t)0x%08x) (con->frame.id (drawable_t)0x%08x)\n", width, height, con, con->frame_buffer.id, con->frame.id);
xcb_create_pixmap(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height);
draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id,
get_visualtype_by_id(get_visualid_by_depth(win_depth)), width, height);

View File

@ -798,6 +798,8 @@ sub exit_gracefully {
my ($pid, $socketpath) = @_;
$socketpath ||= get_socket_path();
$SIG{CHLD} = undef;
my $exited = 0;
eval {
say "Exiting i3 cleanly...";
@ -836,6 +838,8 @@ sub exit_forcefully {
my ($pid, $signal) = @_;
$signal ||= 'TERM';
$SIG{CHLD} = undef;
# Send the given signal to the i3 instance and wait for up to 10s
# for it to terminate.
kill($signal, $pid)
@ -959,6 +963,18 @@ sub launch_with_config {
return ${^CHILD_ERROR_NATIVE};
}
$SIG{CHLD} = sub {
# don't change $! and $? outside handler
local ($!, $?);
my $child = waitpid -1, POSIX::WNOHANG;
warn "SIGCHLD, waitpid() = $child";
if ($child == $i3_pid) {
warn "i3 died, exiting!";
exit 1;
}
};
# force update of the cached socket path in lib/i3test
# as soon as i3 has started
$cv->cb(sub { get_socket_path(0) });

View File

@ -326,6 +326,42 @@ is_num_children('left-bottom', 2, 'two children on left-bottom');
kill_all_windows;
exit_gracefully($pid);
#####################################################################
# Test assignments to primary / nonprimary outputs
#####################################################################
$config = <<'EOT';
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
fake-outputs 1024x768+0+0P,1024x768+1024+0
workspace primary output fake-0
workspace nonprimary output fake-1
assign [class="current"] output current
assign [class="^primary$"] output primary
assign [class="nonprimary"] output nonprimary
EOT
$pid = launch_with_config($config);
cmd 'workspace primary';
open_special(wm_class => 'current');
sync_with_i3;
is_num_children('primary', 1, 'one window in current workspace');
open_special(wm_class => 'nonprimary');
sync_with_i3;
is_num_children('nonprimary', 1, 'one child on nonprimary');
cmd 'workspace nonprimary';
open_special(wm_class => 'primary');
sync_with_i3;
is_num_children('primary', 2, 'two children on primary');
kill_all_windows;
exit_gracefully($pid);
#####################################################################
# regression test: dock clients with floating assignments should not crash
# (instead, nothing should happen - dock clients cant float)

View File

@ -121,6 +121,12 @@ bar {
urgent_workspace #2f343a #900000 #ffffff
binding_mode #abc123 #123abc #ababab
}
# old syntax for compatibility with i3-gaps
height 50
# new syntax: top right bottom left
# y width height x
padding 4px 2px 4px 2px
}
EOT
@ -145,6 +151,14 @@ is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok');
is($bar_config->{tray_padding}, 0, 'tray_padding ok');
is($bar_config->{font}, 'Terminus', 'font ok');
is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok');
is($bar_config->{bar_height}, 50, 'bar_height ok');
is_deeply($bar_config->{padding},
{
x => 2,
y => 4,
width => 2,
height => 4,
}, 'padding ok');
is_deeply($bar_config->{colors},
{
background => '#ff0000',
@ -171,6 +185,105 @@ is_deeply($bar_config->{colors},
exit_gracefully($pid);
#####################################################################
# validate one-value padding directive
#####################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bar {
# all padding is 25px
padding 25px
}
EOT
$pid = launch_with_config($config);
$i3 = i3(get_socket_path(0));
$bars = $i3->get_bar_config()->recv;
is(@$bars, 1, 'one bar configured');
$bar_id = shift @$bars;
$bar_config = $i3->get_bar_config($bar_id)->recv;
is_deeply($bar_config->{padding},
{
x => 25,
y => 25,
width => 25,
height => 25,
}, 'padding ok');
exit_gracefully($pid);
#####################################################################
# validate two-value padding directive
#####################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bar {
# top and bottom padding is 25px, right and left padding is 50px
padding 25px 50px
}
EOT
$pid = launch_with_config($config);
$i3 = i3(get_socket_path(0));
$bars = $i3->get_bar_config()->recv;
is(@$bars, 1, 'one bar configured');
$bar_id = shift @$bars;
$bar_config = $i3->get_bar_config($bar_id)->recv;
is_deeply($bar_config->{padding},
{
x => 50,
y => 25,
width => 50,
height => 25,
}, 'padding ok');
exit_gracefully($pid);
#####################################################################
# validate three-value padding directive
#####################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bar {
# top padding is 25px, right and left padding is 50px, bottom padding is 75px
padding 25px 50px 75px
}
EOT
$pid = launch_with_config($config);
$i3 = i3(get_socket_path(0));
$bars = $i3->get_bar_config()->recv;
is(@$bars, 1, 'one bar configured');
$bar_id = shift @$bars;
$bar_config = $i3->get_bar_config($bar_id)->recv;
is_deeply($bar_config->{padding},
{
x => 50,
y => 25,
width => 50,
height => 75,
}, 'padding ok');
exit_gracefully($pid);
#####################################################################
# ensure that multiple bars get different IDs
#####################################################################

View File

@ -106,30 +106,35 @@ is(parser_calls('resize shrink left 25 px or 33 ppt;'),
is(parser_calls('[con_mark=yay] focus'),
"cmd_criteria_add(con_mark, yay)\n" .
"cmd_focus()",
"cmd_focus(0)",
'criteria focus ok');
is(parser_calls('[con_mark=yay] focus workspace'),
"cmd_criteria_add(con_mark, yay)\n" .
"cmd_focus(1)",
'criteria focus workspace ok');
is(parser_calls("[con_mark=yay con_mark=bar] focus"),
"cmd_criteria_add(con_mark, yay)\n" .
"cmd_criteria_add(con_mark, bar)\n" .
"cmd_focus()",
"cmd_focus(0)",
'criteria focus ok');
is(parser_calls("[con_mark=yay\tcon_mark=bar] focus"),
"cmd_criteria_add(con_mark, yay)\n" .
"cmd_criteria_add(con_mark, bar)\n" .
"cmd_focus()",
"cmd_focus(0)",
'criteria focus ok');
is(parser_calls("[con_mark=yay\tcon_mark=bar]\tfocus"),
"cmd_criteria_add(con_mark, yay)\n" .
"cmd_criteria_add(con_mark, bar)\n" .
"cmd_focus()",
"cmd_focus(0)",
'criteria focus ok');
is(parser_calls('[con_mark="yay"] focus'),
"cmd_criteria_add(con_mark, yay)\n" .
"cmd_focus()",
"cmd_focus(0)",
'quoted criteria focus ok');
# Make sure trailing whitespace is stripped off: While this is not an issue for
@ -179,6 +184,7 @@ is(parser_calls('unknown_literal'),
title_window_icon
mode
bar
gaps
)) . "'\n" .
"ERROR: Your command: unknown_literal\n" .
"ERROR: ^^^^^^^^^^^^^^^",

View File

@ -515,6 +515,9 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
bar
font
mode
gaps
smart_borders
smart_gaps
floating_minimum_size
floating_maximum_size
floating_modifier
@ -549,6 +552,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
ipc_kill_timeout
restart_state
popup_during_fullscreen
tiling_drag
exec_always
exec
client.background
@ -580,7 +584,7 @@ client.focused #4c7899 #285577 #ffffff #2e9ef4
EOT
$expected = <<'EOT';
ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active'
ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active'
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: hide_edge_borders FOOBAR
ERROR: CONFIG: ^^^^^^
@ -772,7 +776,7 @@ EOT
$expected = <<'EOT';
cfg_bar_start()
cfg_bar_output(LVDS-1)
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'workspace_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'height', 'padding', 'colors', '}'
ERROR: CONFIG: (in file <stdin>)
ERROR: CONFIG: Line 1: bar {
ERROR: CONFIG: Line 2: output LVDS-1

View File

@ -16,6 +16,7 @@
#
# Test that the binding event works properly
# Ticket: #1210
# Ticket: #5323
use i3test i3_autostart => 0;
my $keysym = 't';
@ -28,6 +29,12 @@ my $config = <<EOT;
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bindsym $binding_symbol $command
bindsym m mode some-mode
mode some-mode {
bindsym a nop
bindsym b mode default
}
EOT
SKIP: {
@ -46,12 +53,13 @@ SKIP: {
qx(xdotool key $binding_symbol);
},
'binding');
is(scalar @events, 1, 'Received 1 event');
is($events[0]->{change}, 'run',
'the `change` field should indicate this binding has run');
is($events[0]->{mode}, 'default', 'the `mode` field should be "default"');
ok($events[0]->{binding},
'the `binding` field should be a hash that contains information about the binding');
@ -69,6 +77,21 @@ SKIP: {
is($events[0]->{binding}->{input_code}, 0,
'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
# Test that the mode field is correct
# See #5323.
@events = events_for(
sub {
qx(xdotool key m);
qx(xdotool key a);
qx(xdotool key b);
},
'binding');
is(scalar @events, 3, 'Received 3 events');
is($events[0]->{mode}, 'default', 'Event for binding to enter new mode is atributed to default mode');
is($events[1]->{mode}, 'some-mode', 'Event for binding while in mode is attributed to the non-default mode');
is($events[2]->{mode}, 'some-mode', 'Event for binding exiting mode is attributed to the non-default mode');
exit_gracefully($pid);
}

View File

@ -50,7 +50,7 @@ $target = get_focused($ws);
$A = $cons[0];
$C = $cons[1]->{nodes}[1];
$y = $C->{rect}->{y} - 0.5 * $C->{deco_rect}->{height};
$y = $C->{rect}->{y} + 0.5 * $C->{deco_rect}->{height};
# make sure that B is the focus head of its parent
cmd '[id="' . $B->{id} . '"] focus';

View File

@ -14,7 +14,10 @@
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
use i3test i3_config => <<EOT;
use i3test i3_autostart => 0;
use X11::XCB qw(PROP_MODE_REPLACE);
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
for_window [tiling] mark --add tiling
@ -26,7 +29,8 @@ for_window [floating_from="auto"] mark --add floating_auto
for_window [tiling_from="user"] mark --add tiling_user
for_window [floating_from="user"] mark --add floating_user
EOT
use X11::XCB qw(PROP_MODE_REPLACE);
my $pid = launch_with_config($config);
##############################################################
# Check that the auto tiling / floating criteria work.
@ -83,4 +87,45 @@ is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'B', 'floating_user' ], "Only 'float
##############################################################
exit_gracefully($pid);
################################################################################
# Verify floating_from/tiling_from works as command criterion (issue #5258).
################################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
for_window [tiling] mark --add tiling
for_window [floating] mark --add floating
EOT
$pid = launch_with_config($config);
$tmp = fresh_workspace;
$A = open_window;
$B = open_floating_window;
@nodes = @{get_ws($tmp)->{nodes}};
cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace');
is_deeply($nodes[0]->{marks}, [ 'tiling' ], "mark set for 'tiling' criterion");
@nodes = @{get_ws($tmp)->{floating_nodes}};
cmp_ok(@nodes, '==', 1, 'one floating container on this workspace');
is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floating' ], "mark set for 'floating' criterion");
cmd '[tiling_from="auto" con_mark="tiling"] mark --add tiling_auto';
cmd '[floating_from="auto" con_mark="floating"] mark --add floating_auto';
@nodes = @{get_ws($tmp)->{nodes}};
cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace');
is_deeply($nodes[0]->{marks}, [ 'tiling', 'tiling_auto' ], "mark set for 'tiling' criterion");
@nodes = @{get_ws($tmp)->{floating_nodes}};
cmp_ok(@nodes, '==', 1, 'one floating container on this workspace');
is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floating', 'floating_auto' ], "mark set for 'floating' criterion");
exit_gracefully($pid);
done_testing;

View File

@ -160,4 +160,69 @@ is($tilewindow2->rect->width, $tiled[1]->{rect}->{width} - 2*2, 'second tiled bo
exit_gracefully($pid);
#####################################################################
# 5: check that the borders are visible on a workspace with one tiled
# window and edge gaps
#####################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
new_window pixel 2
new_float pixel 2
gaps outer 5
hide_edge_borders smart_no_gaps
EOT
$pid = launch_with_config($config);
$tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
$tilewindow = open_window;
$wscontent = get_ws($tmp);
@tiled = @{$wscontent->{nodes}};
ok(@tiled == 1, 'one tiled container opened');
is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2');
is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*2, 'single tiled border width 2');
exit_gracefully($pid);
#####################################################################
# 5: check that the borders are hidden on a workspace with one tiled
# window with no gaps
#####################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
new_window pixel 2
new_float pixel 2
hide_edge_borders smart_no_gaps
EOT
$pid = launch_with_config($config);
$tmp = fresh_workspace;
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
$tilewindow = open_window;
$wscontent = get_ws($tmp);
@tiled = @{$wscontent->{nodes}};
ok(@tiled == 1, 'one tiled container opened');
is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2');
is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*0, 'single tiled border width 0');
exit_gracefully($pid);
done_testing;

View File

@ -21,7 +21,9 @@
#
# Ticket: #2318
# Bug still in: 4.12-46-g2123888
use i3test;
use i3test i3_autostart => 0;
my $pid = launch_with_config('-default');
# We cannot use events_for in this test as we cannot send events after
# issuing the restart/shutdown command.
@ -59,7 +61,7 @@ $i3->subscribe({
}
})->recv;
cmd 'exit';
exit_gracefully($pid);
$e = $cv->recv;

View File

@ -86,6 +86,8 @@ workspace 5 output fake-0
workspace 5:xxx output fake-1
workspace 6:xxx output fake-0
workspace 6 output fake-1
workspace 7 output nonprimary primary
workspace 8 output doesnotexist primary
EOT
$pid = launch_with_config($config);
@ -98,7 +100,9 @@ do_test('5', 'fake-0', 'Numbered assignment ok');
do_test('5:xxx', 'fake-1', 'Named assignment overrides number');
do_test('6', 'fake-1', 'Numbered assignment ok');
do_test('6:xxx', 'fake-0', 'Named assignment overrides number');
do_test('7', 'fake-2', 'Numbered initialization for fake-2');
do_test('7', 'fake-1', 'Non-primary output');
do_test('8', 'fake-0', 'Primary output');
do_test('9', 'fake-2', 'Numbered initialization for fake-2');
cmd 'focus output fake-0, workspace foo';
check_output('foo', 'fake-0', 'Workspace with only non-existing assigned outputs opened in current output');

Some files were not shown because too many files have changed in this diff Show More