Merge branch 'next' into stable
This commit is contained in:
commit
c0ef3caec8
13
.github/CONTRIBUTING.md
vendored
13
.github/CONTRIBUTING.md
vendored
@ -41,11 +41,14 @@ Note that bug reports and feature requests for related projects should be filed
|
||||
* Use `clang-format` to format your code.
|
||||
* Run the [testsuite](https://i3wm.org/docs/testsuite.html)
|
||||
* If your changes should be reported on the next release's changelog, also
|
||||
update the [RELEASE-notes-next](../RELEASE-notes-next) file in the root
|
||||
folder. Example of changes that should be reported are bug fixes present in
|
||||
the latest stable version of i3 and new enhancements. Example of changes that
|
||||
should not be reported are minor code improvements, documentation, regression
|
||||
and fixes for bugs that were introduced in the `next` branch.
|
||||
add a small single-line file starting with a number (see examples) containing
|
||||
a short explanation of your change either in the
|
||||
[changes](../release-notes/changes) or the
|
||||
[bugfixes](../release-notes/bugfixes/) folder. Example of changes that should
|
||||
be reported are bug fixes present in the latest stable version of i3 and new
|
||||
enhancements. Example of changes that should not be reported are minor code
|
||||
improvements, documentation, regression and fixes for bugs that were
|
||||
introduced in the `next` branch.
|
||||
|
||||
## Finding something to do
|
||||
|
||||
|
20
.github/workflows/main.yml
vendored
20
.github/workflows/main.yml
vendored
@ -45,12 +45,6 @@ jobs:
|
||||
echo "::group::Ubuntu i386"
|
||||
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU_386 }} travis/travis-base-ubuntu-386.Dockerfile
|
||||
echo "::endgroup::"
|
||||
- name: verify safe wrapper functions are used
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-safe-wrappers.sh
|
||||
- name: verify code formatting
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-formatting.sh
|
||||
- name: build i3
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
|
||||
@ -92,3 +86,17 @@ jobs:
|
||||
- name: push docs to GitHub pages
|
||||
run: |
|
||||
./travis/skip-pkg.sh || travis/deploy-github-pages.sh
|
||||
formatting:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: check & print release notes
|
||||
run: ./release-notes/generator.pl
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -y clang-format-10
|
||||
- name: Check formatting
|
||||
run: clang-format-10 --dry-run --Werror $(git ls-files '*.c' '*.h')
|
||||
- name: Verify safe wrapper functions are used
|
||||
run: ./travis/check-safe-wrappers.sh
|
||||
|
@ -1,27 +0,0 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.20.1 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.20.1. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: fix crash with multiple monitors
|
||||
• xmlto: fix broken .TH line by extending title length
|
||||
• i3-msg: fix --raw short form (-r) in manpage
|
||||
• libi3: add missing sys/stat.h header
|
||||
• use getcwd(NULL, 0) instead of GNU extension get_current_dir_name()
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
rvalieris, Jakob Haufe, lycurgus, Baptiste Daroussin
|
||||
|
||||
-- Michael Stapelberg, 2021-11-03
|
74
RELEASE-NOTES-4.21
Normal file
74
RELEASE-NOTES-4.21
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.21 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.21. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
The biggest change in this release is that you can now drag tiling windows
|
||||
with your mouse (floating windows could already be dragged). For more details
|
||||
on how to use this feature, please refer to the userguide:
|
||||
|
||||
https://i3wm.org/docs/userguide.html#_moving_tiling_containers_with_the_mouse
|
||||
|
||||
A big thank you goes out to our core i3 developer Orestis Floros who made this
|
||||
feature possible, based on previous work from Michael Forster and Tony Crisci!
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in i3 v4.21 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• Allow dragging tiling windows with the mouse
|
||||
• Add client.focused_tab_title color option
|
||||
• Add support for multiple output names in the focus command,
|
||||
allowing users to cycle focus between e.g. VGA1 and LVDS1 but not DVI0.
|
||||
• Add a toggle option to the title_window_icon command
|
||||
• i3 switched from the obsolete PCRE 8.x regular expression matching
|
||||
library to the current PCRE2 10.x version.
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• docs/ipc: document all window_type values
|
||||
• docs/userguide: clarify the difference between the “workspace N” and
|
||||
“workspace number N” commands
|
||||
• i3bar: fix default font not being applied to bars if defined after bar block
|
||||
• i3-dmenu-desktop: add backslashes for the exec command,
|
||||
which fixes opening some .desktop files (e.g. electrum)
|
||||
• i3-sensible-pager: sanitize LESS environment variable to remove -E or -F
|
||||
• testsuite: catch i3 crashes instead of hanging on crash
|
||||
• Fix logging on machines with 256 GB of RAM
|
||||
• Do not replace existing IPC socket on start, to prevent clobbering
|
||||
the IPC socket when running i3 within i3 (e.g. in Xepyhr, for development)
|
||||
• Refuse to start without a valid IPC socket
|
||||
• Fix focus when moving container between outputs with mouse warp and
|
||||
focus_follows_mouse
|
||||
• Fix endless loop with transient_for windows
|
||||
• Fix wrong “failed” IPC reply on move workspace to output
|
||||
• Fix WM registration selection (from WM_S_S<screen> to WM_S<screen>)
|
||||
• avoid graphics artifacts when changing the layout tree by
|
||||
initializing surfaces to all black
|
||||
• update parent split con titles when child container swaps position with
|
||||
another child container
|
||||
• Fix segfault if command in bindsym is empty
|
||||
• Fix segfault with explicit mode "default" key bindings
|
||||
• Fix crash if config contains nested variables.
|
||||
• strip trailing whitespace in bar output names
|
||||
• Fix crash with long commands
|
||||
• Fix changing borders by restoring BS_NORMAL _MOTIF_WM_HINTS correctly
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
André Silva, Anton Älgmyr, Baptiste Daroussin, bodea, Chris Templin, George
|
||||
Rodrigues, Gergely Risko, Ingo Bürk, Jakob Haufe, Jay Ta'ala, Jeff Smith, Jonta,
|
||||
Josh Soref, Kjetil Torgrim Homme, lycurgus, mariano, Michael Forster, Orestis
|
||||
Floros, paperluigis, Peder Stray, rvalieris, sergio, Tony Crisci, takelley1, Uli
|
||||
Schlachter, viri, zhiv-git, zhrvn
|
||||
|
||||
-- Michael Stapelberg, 2021-10-19
|
8
debian/changelog
vendored
8
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
i3-wm (4.20.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 03 Nov 2021 09:22:48 +0100
|
||||
|
||||
i3-wm (4.20-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
@ -621,7 +627,7 @@ i3-wm (3.d-bf1-1) unstable; urgency=low
|
||||
* Bugfix: Resize client after updating base_width/base_height
|
||||
* Bugfix: Force render containers after setting the client active
|
||||
* Bugfix: Fix two problems in resizing floating windows with right mouse
|
||||
* Bugfix: Use more precise floating point arithmetics
|
||||
* Bugfix: Use more precise floating point arithmetic
|
||||
* Bugfix: Correctly place new windows below fullscreen windows
|
||||
|
||||
-- Michael Stapelberg <michael@stapelberg.de> Mon, 21 Dec 2009 22:33:02 +0100
|
||||
|
2
debian/control
vendored
2
debian/control
vendored
@ -22,7 +22,7 @@ Build-Depends: debhelper (>= 10),
|
||||
pkg-config,
|
||||
libev-dev (>= 1:4.04),
|
||||
libyajl-dev (>= 2.0.4),
|
||||
libpcre3-dev (>= 1:8.10),
|
||||
libpcre2-dev,
|
||||
libstartup-notification0-dev (>= 0.10),
|
||||
libcairo2-dev (>= 1.14.4),
|
||||
libpango1.0-dev,
|
||||
|
4
debian/rules
vendored
4
debian/rules
vendored
@ -17,9 +17,5 @@ override_dh_auto_configure:
|
||||
# Set -Ddocdir; the default is /usr/share/doc/i3
|
||||
dh_auto_configure -- -Ddocdir=/usr/share/doc/i3-wm -Dmans=true
|
||||
|
||||
override_dh_builddeb:
|
||||
# bintray does not support xz currently.
|
||||
dh_builddeb -- -Zgzip
|
||||
|
||||
%:
|
||||
dh $@ --buildsystem=meson
|
||||
|
6
docs/ipc
6
docs/ipc
@ -423,9 +423,9 @@ window_properties (map)::
|
||||
following list: *title*, *instance*, *class*, *window_role*, *machine*
|
||||
and *transient_for*.
|
||||
window_type (string)::
|
||||
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
|
||||
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
|
||||
notification.
|
||||
The window type (_NET_WM_WINDOW_TYPE). Possible values are `undefined`,
|
||||
unknown, normal, dialog, utility, toolbar, splash, menu, dropdown_menu,
|
||||
popup_menu, tooltip and notification.
|
||||
urgent (bool)::
|
||||
Whether this container (window, split container, floating container or
|
||||
workspace) has the urgency hint set, directly or indirectly. All parent
|
||||
|
@ -4,11 +4,10 @@ Michael Stapelberg <michael@i3wm.org>
|
||||
September 2012
|
||||
|
||||
This document explains how the i3 testsuite works, how to use it and extend it.
|
||||
It is targeted at developers who not necessarily have been doing testing before
|
||||
or have not been testing in Perl before. In general, the testsuite is not of
|
||||
It is targeted at developers who haven't necessarily done testing before,
|
||||
or have not used Perl for testing before. In general, the testsuite is not of
|
||||
interest for end users.
|
||||
|
||||
|
||||
== Introduction
|
||||
|
||||
The i3 testsuite is a collection of files which contain testcases for various
|
||||
|
@ -196,6 +196,48 @@ provided by the i3 https://github.com/i3/i3/blob/next/etc/config.keycodes[defaul
|
||||
|
||||
Floating windows are always on top of tiling windows.
|
||||
|
||||
=== Moving tiling containers with the mouse
|
||||
|
||||
Since i3 4.21, it's possible to drag tiling containers using the mouse. The
|
||||
drag can be initiated either by dragging the window's titlebar or by pressing
|
||||
the <<floating_modifier>> and dragging the container while holding the
|
||||
left-click button.
|
||||
|
||||
Once the drag is initiated and the cursor has left the original container, drop
|
||||
indicators are created according to the position of the cursor relatively to
|
||||
the target container. These indicators help you understand what the resulting
|
||||
<<tree>> layout is going to be after you release the mouse button.
|
||||
|
||||
The possible drop positions are:
|
||||
|
||||
Drop on container::
|
||||
This happens when the mouse is relatively near the center of a container.
|
||||
If the mouse is released, the result is exactly as if you had run the
|
||||
+move container to mark+ command. See <<move_to_mark>>.
|
||||
Drop as sibling::
|
||||
This happens when the mouse is relatively near the edge of a container. If
|
||||
the mouse is released, the dragged container will become a sibling of the
|
||||
target container, placed left/right/up/down according to the position of
|
||||
the indicator.
|
||||
This might or might not create a new v-split or h-split according to the
|
||||
previous layout of the target container. For example, if the target
|
||||
container is in an h-split and you drop the dragged container below it, the
|
||||
new layout will have to be a v-split.
|
||||
Drop to parent::
|
||||
This happens when the mouse is relatively near the edge of a container (but
|
||||
even closer to the border in comparison to the sibling case above) *and* if
|
||||
that edge is also the parent container's edge. For example, if three
|
||||
containers are in a horizontal layout then edges where this can happen is
|
||||
the left edge of the left container, the right edge of the right container
|
||||
and all bottom and top edges of all three containers.
|
||||
If the mouse is released, the container is first dropped as a sibling to
|
||||
the target container, like in the case above, and then is moved
|
||||
directionally like with the +move left|right|down|up+ command. See
|
||||
<<move_direction>>.
|
||||
|
||||
The color of the indicator matches the +client.focused+ setting. See <<client_colors>>.
|
||||
|
||||
[[tree]]
|
||||
== Tree
|
||||
|
||||
i3 stores all information about the X11 outputs, workspaces and layout of the
|
||||
@ -538,7 +580,7 @@ for the keybinding.
|
||||
# The middle button over a titlebar kills the window
|
||||
bindsym --release button2 kill
|
||||
|
||||
# The middle button and a modifer over any part of the window kills the window
|
||||
# The middle button and a modifier over any part of the window kills the window
|
||||
bindsym --whole-window $mod+button2 kill
|
||||
|
||||
# The right button toggles floating
|
||||
@ -1041,6 +1083,7 @@ workspace 5 output VGA1 LVDS1
|
||||
workspace "2: vim" output VGA1
|
||||
---------------------------
|
||||
|
||||
[[client_colors]]
|
||||
=== Changing colors
|
||||
|
||||
You can change all colors which i3 uses to draw the window decorations.
|
||||
@ -1057,6 +1100,10 @@ client.focused::
|
||||
client.focused_inactive::
|
||||
A client which is the focused one of its container, but it does not have
|
||||
the focus at the moment.
|
||||
client.focused_tab_title::
|
||||
Tab or stack container title that is the parent of the focused container
|
||||
but not directly focused. Defaults to focused_inactive if not specified and
|
||||
does not use the indicator and child_border colors.
|
||||
client.unfocused::
|
||||
A client which is not the focused one of its container.
|
||||
client.urgent::
|
||||
@ -1100,7 +1147,7 @@ i3 uses Unix sockets to provide an IPC interface. This allows third-party
|
||||
programs to get information from i3, such as the current workspaces
|
||||
(to display a workspace bar), and to control i3.
|
||||
|
||||
The IPC socket is enabled by default and will be created in
|
||||
By default, an IPC socket will be created in
|
||||
+$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available, falling back
|
||||
to +/tmp/i3-%u.XXXXXX/ipc-socket.%p+, where +%u+ is your UNIX username, +%p+ is
|
||||
the PID of i3 and XXXXXX is a string of random characters from the portable
|
||||
@ -2208,7 +2255,7 @@ output::
|
||||
focus left|right|down|up
|
||||
focus parent|child|floating|tiling|mode_toggle
|
||||
focus next|prev [sibling]
|
||||
focus output left|right|up|down|primary|<output>
|
||||
focus output left|right|down|up|current|primary|next|<output1> [output2]…
|
||||
----------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
@ -2228,6 +2275,9 @@ bindsym $mod+u focus parent
|
||||
# Focus last floating/tiling container
|
||||
bindsym $mod+g focus mode_toggle
|
||||
|
||||
# Focus the next output (effectively toggles when you only have two outputs)
|
||||
bindsym $mod+x move workspace to output next
|
||||
|
||||
# Focus the output right to the current one
|
||||
bindsym $mod+x focus output right
|
||||
|
||||
@ -2236,6 +2286,9 @@ bindsym $mod+x focus output HDMI-2
|
||||
|
||||
# Focus the primary output
|
||||
bindsym $mod+x focus output primary
|
||||
|
||||
# Cycle focus between outputs VGA1 and LVDS1 but not DVI0
|
||||
bindsym $mod+x move workspace to output VGA1 LVDS1
|
||||
-------------------------------------------------
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
@ -2243,6 +2296,7 @@ Note that you might not have a primary output configured yet. To do so, run:
|
||||
xrandr --output <output> --primary
|
||||
-------------------------
|
||||
|
||||
[[move_direction]]
|
||||
=== Moving containers
|
||||
|
||||
Use the +move+ command to move a container.
|
||||
@ -2477,9 +2531,11 @@ bindsym $mod+2 workspace number "2: mail"
|
||||
If a workspace does not exist, the command +workspace number "1: mail"+ will
|
||||
create workspace "1: mail".
|
||||
|
||||
If a workspace with number 1 does already exist, the command will switch to this
|
||||
If a workspace with number 1 already exists, the command will switch to this
|
||||
workspace and ignore the text part. So even when the workspace has been renamed
|
||||
to "1: web", the above command will still switch to it.
|
||||
"1: web", the above command will still switch to it. The command +workspace 1+
|
||||
will however create and move to a new workspace "1" alongside the existing
|
||||
"1: mail" workspace.
|
||||
|
||||
=== Moving workspaces to a different screen
|
||||
|
||||
@ -2525,6 +2581,7 @@ Note that you might not have a primary output configured yet. To do so, run:
|
||||
xrandr --output <output> --primary
|
||||
-------------------------
|
||||
|
||||
[[move_to_mark]]
|
||||
=== Moving containers/windows to marks
|
||||
|
||||
To move a container to another container with a specific mark (see <<vim_like_marks>>),
|
||||
@ -2718,8 +2775,8 @@ specific windows or for all windows (using the <<for_window>> directive).
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------
|
||||
title_window_icon <yes|no>
|
||||
title_window_icon padding <px>
|
||||
title_window_icon <yes|no|toggle>
|
||||
title_window_icon <padding|toggle> <px>
|
||||
------------------------------
|
||||
|
||||
*Examples*:
|
||||
|
@ -228,8 +228,15 @@ for my $state (@keys) {
|
||||
($call_identifier) = ($next_state =~ /^call ([0-9]+)$/);
|
||||
$next_state = '__CALL';
|
||||
}
|
||||
my $identifier = $token->{identifier};
|
||||
say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
|
||||
my $identifier;
|
||||
# Set $identifier to NULL if there is no identifier
|
||||
if ($token->{identifier} eq ""){
|
||||
$identifier = "NULL"
|
||||
}
|
||||
else{
|
||||
$identifier = qq|"$token->{identifier}"|;
|
||||
}
|
||||
say $tokfh qq| { "$token_name", $identifier, $next_state, { $call_identifier } },|;
|
||||
}
|
||||
say $tokfh '};';
|
||||
}
|
||||
|
@ -332,8 +332,6 @@ static char *rewrite_binding(const char *input) {
|
||||
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
|
||||
walk++;
|
||||
|
||||
//printf("remaining input: %s\n", walk);
|
||||
|
||||
cmdp_token_ptr *ptr = &(tokens[state]);
|
||||
for (c = 0; c < ptr->n; c++) {
|
||||
token = &(ptr->array[c]);
|
||||
@ -426,15 +424,13 @@ static char *rewrite_binding(const char *input) {
|
||||
}
|
||||
|
||||
if (strcmp(token->name, "end") == 0) {
|
||||
//printf("checking for end: *%s*\n", walk);
|
||||
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
|
||||
if ((result = next_state(token)) != NULL)
|
||||
return result;
|
||||
/* To make sure we start with an appropriate matching
|
||||
* datastructure for commands which do *not* specify any
|
||||
/* To make sure we start with an appropriate matching data
|
||||
* structure for commands which do *not* specify any
|
||||
* criteria, we re-initialize the criteria system after
|
||||
* every command. */
|
||||
// TODO: make this testable
|
||||
walk++;
|
||||
break;
|
||||
}
|
||||
|
@ -482,7 +482,8 @@ EOT
|
||||
# double quote which is NOT preceded by a backslash (\).
|
||||
#
|
||||
# Therefore, we escape all double quotes (") by replacing them with \"
|
||||
$exec =~ s/"/\\"/g;
|
||||
$exec =~ s/\\"/\\\\\\"/g;
|
||||
$exec =~ s/([^\\])"/$1\\"/g;
|
||||
|
||||
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
|
||||
$nosn = '--no-startup-id';
|
||||
|
@ -1,6 +1,6 @@
|
||||
# This list can be used to convert X11 Keysyms to Unicode 2.1 character.
|
||||
# The list is not checked for correctness by Unicode officials. Use it
|
||||
# at your own risk and the creator is not responsable for any damage that
|
||||
# at your own risk and the creator is not responsible for any damage that
|
||||
# occurred due to using this list.
|
||||
#
|
||||
# The list is created by looking at the Keysym names and the Unicode data
|
||||
|
@ -8,6 +8,11 @@
|
||||
# Distributions/packagers can enhance this script with a
|
||||
# distribution-specific mechanism to find the preferred pager.
|
||||
|
||||
# The less -E and -F options exit immediately for short files, strip if present.
|
||||
case "$LESS" in
|
||||
*[EF]*) LESS=`echo "$LESS" | tr -d EF`
|
||||
esac
|
||||
|
||||
# Hopefully one of these is installed (no flamewars about preference please!):
|
||||
# We don't use 'more' because it will exit if the file is too short.
|
||||
# Worst case scenario we'll open the file in your editor.
|
||||
|
@ -14,7 +14,7 @@
|
||||
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
|
||||
# 3. The terminal emulator with best accessibility comes first.
|
||||
# 4. No order is guaranteed/desired for the remaining terminal emulators.
|
||||
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
|
||||
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm; do
|
||||
if command -v "$terminal" > /dev/null 2>&1; then
|
||||
exec "$terminal" "$@"
|
||||
fi
|
||||
|
@ -66,7 +66,7 @@ struct status_block {
|
||||
uint32_t border_left;
|
||||
bool pango_markup;
|
||||
|
||||
/* The amount of pixels necessary to render a separater after the block. */
|
||||
/* The amount of pixels necessary to render a separator after the block. */
|
||||
uint32_t sep_block_width;
|
||||
|
||||
/* Continuously-updated information on how to render this status block. */
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <yajl/yajl_parse.h>
|
||||
|
||||
config_t config;
|
||||
@ -126,31 +125,31 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
||||
}
|
||||
|
||||
if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
|
||||
config.modifier = ShiftMask;
|
||||
config.modifier = XCB_MOD_MASK_SHIFT;
|
||||
return 1;
|
||||
}
|
||||
if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
|
||||
config.modifier = ControlMask;
|
||||
config.modifier = XCB_MOD_MASK_CONTROL;
|
||||
return 1;
|
||||
}
|
||||
if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
|
||||
switch (val[3]) {
|
||||
case '1':
|
||||
config.modifier = Mod1Mask;
|
||||
config.modifier = XCB_MOD_MASK_1;
|
||||
return 1;
|
||||
case '2':
|
||||
config.modifier = Mod2Mask;
|
||||
config.modifier = XCB_MOD_MASK_2;
|
||||
return 1;
|
||||
case '3':
|
||||
config.modifier = Mod3Mask;
|
||||
config.modifier = XCB_MOD_MASK_3;
|
||||
return 1;
|
||||
case '5':
|
||||
config.modifier = Mod5Mask;
|
||||
config.modifier = XCB_MOD_MASK_5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
config.modifier = Mod4Mask;
|
||||
config.modifier = XCB_MOD_MASK_4;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -203,7 +203,7 @@ static uint32_t predict_statusline_length(bool use_short_text) {
|
||||
if (block->border)
|
||||
render->width += logical_px(block->border_left + block->border_right);
|
||||
|
||||
/* Compute offset and append for text aligment in min_width. */
|
||||
/* Compute offset and append for text alignment in min_width. */
|
||||
if (block->min_width <= render->width) {
|
||||
render->x_offset = 0;
|
||||
render->x_append = 0;
|
||||
|
@ -58,6 +58,7 @@
|
||||
#include "match.h"
|
||||
#include "xcursor.h"
|
||||
#include "resize.h"
|
||||
#include "tiling_drag.h"
|
||||
#include "sighandler.h"
|
||||
#include "move.h"
|
||||
#include "output.h"
|
||||
|
@ -333,7 +333,7 @@ void cmd_shmlog(I3_CMD, const char *argument);
|
||||
void cmd_debuglog(I3_CMD, const char *argument);
|
||||
|
||||
/**
|
||||
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
|
||||
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_title_window_icon(I3_CMD, const char *enable, int padding);
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include <yajl/yajl_gen.h>
|
||||
|
||||
/**
|
||||
* Holds an intermediate represenation of the result of a call to any command.
|
||||
* Holds an intermediate representation of the result of a call to any command.
|
||||
* When calling parse_command("floating enable, border none"), the parser will
|
||||
* internally use this struct when calling cmd_floating and cmd_border.
|
||||
*/
|
||||
|
@ -208,6 +208,14 @@ Con *con_by_frame_id(xcb_window_t frame);
|
||||
*/
|
||||
Con *con_by_mark(const char *mark);
|
||||
|
||||
/**
|
||||
* Start from a container and traverse the transient_for linked list. Returns
|
||||
* true if target window is found in the list. Protects againsts potential
|
||||
* cycles.
|
||||
*
|
||||
*/
|
||||
bool con_find_transient_for_window(Con *start, xcb_window_t target);
|
||||
|
||||
/**
|
||||
* Returns true if and only if the given containers holds the mark.
|
||||
*
|
||||
@ -363,6 +371,7 @@ void con_move_to_output(Con *con, Output *output, bool fix_coordinates);
|
||||
*/
|
||||
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
|
||||
|
||||
bool con_move_to_target(Con *con, Con *target);
|
||||
/**
|
||||
* Moves the given container to the given mark.
|
||||
*
|
||||
|
@ -41,18 +41,18 @@ struct parser_ctx {
|
||||
Match current_match;
|
||||
|
||||
/* A list which contains the states that lead to the current state, e.g.
|
||||
* INITIAL, WORKSPACE_LAYOUT.
|
||||
* When jumping back to INITIAL, statelist_idx will simply be set to 1
|
||||
* (likewise for other states, e.g. MODE or BAR).
|
||||
* This list is used to process the nearest error token. */
|
||||
* INITIAL, WORKSPACE_LAYOUT.
|
||||
* When jumping back to INITIAL, statelist_idx will simply be set to 1
|
||||
* (likewise for other states, e.g. MODE or BAR).
|
||||
* This list is used to process the nearest error token. */
|
||||
int statelist[10];
|
||||
/* NB: statelist_idx points to where the next entry will be inserted */
|
||||
int statelist_idx;
|
||||
|
||||
/*******************************************************************************
|
||||
* The (small) stack where identified literals are stored during the parsing
|
||||
* of a single config directive (like $workspace).
|
||||
******************************************************************************/
|
||||
* The (small) stack where identified literals are stored during the parsing
|
||||
* of a single config directive (like $workspace).
|
||||
******************************************************************************/
|
||||
struct stack *stack;
|
||||
|
||||
struct variables_head variables;
|
||||
@ -61,7 +61,7 @@ struct parser_ctx {
|
||||
};
|
||||
|
||||
/**
|
||||
* An intermediate reprsentation of the result of a parse_config call.
|
||||
* An intermediate representation of the result of a parse_config call.
|
||||
* Currently unused, but the JSON output will be useful in the future when we
|
||||
* implement a config parsing IPC command.
|
||||
*
|
||||
|
@ -238,9 +238,11 @@ struct Config {
|
||||
color_t background;
|
||||
struct Colortriple focused;
|
||||
struct Colortriple focused_inactive;
|
||||
struct Colortriple focused_tab_title;
|
||||
struct Colortriple unfocused;
|
||||
struct Colortriple urgent;
|
||||
struct Colortriple placeholder;
|
||||
bool got_focused_tab_title;
|
||||
} client;
|
||||
struct config_bar {
|
||||
struct Colortriple focused;
|
||||
|
@ -9,11 +9,13 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
|
||||
#define SN_API_NOT_YET_FROZEN 1
|
||||
#include <libsn/sn-launcher.h>
|
||||
|
||||
#include <xcb/randr.h>
|
||||
#include <pcre.h>
|
||||
#include <pcre2.h>
|
||||
#include <sys/time.h>
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
@ -248,8 +250,7 @@ struct Startup_Sequence {
|
||||
*/
|
||||
struct regex {
|
||||
char *pattern;
|
||||
pcre *regex;
|
||||
pcre_extra *extra;
|
||||
pcre2_code *regex;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -662,8 +663,8 @@ struct Con {
|
||||
char *title_format;
|
||||
|
||||
/** Whether the window icon should be displayed, and with what padding. -1
|
||||
* means display no window icon (default behavior), 0 means display without
|
||||
* any padding, 1 means display with 1 pixel of padding and so on. */
|
||||
* means display no window icon (default behavior), 0 means display without
|
||||
* any padding, 1 means display with 1 pixel of padding and so on. */
|
||||
int window_icon_padding;
|
||||
|
||||
/* a sticky-group is an identifier which bundles several containers to a
|
||||
|
@ -14,7 +14,7 @@
|
||||
/**
|
||||
* Connects to i3 to find out the currently running version. Useful since it
|
||||
* might be different from the version compiled into this binary (maybe the
|
||||
* user didn’t correctly install i3 or forgot te restart it).
|
||||
* user didn’t correctly install i3 or forgot to restart it).
|
||||
*
|
||||
* The output looks like this:
|
||||
* Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
|
||||
|
@ -56,7 +56,6 @@ extern xcb_timestamp_t last_timestamp;
|
||||
extern SnDisplay *sndisplay;
|
||||
extern xcb_key_symbols_t *keysyms;
|
||||
extern char **start_argv;
|
||||
extern Display *xlibdpy, *xkbdpy;
|
||||
extern int xkb_current_group;
|
||||
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
|
||||
extern const char *current_binding_mode;
|
||||
|
@ -294,6 +294,12 @@ size_t i3string_get_num_glyphs(i3String *str);
|
||||
*/
|
||||
int ipc_connect(const char *socket_path);
|
||||
|
||||
/**
|
||||
* Connects to the socket at the given path with no fallback paths. Returns
|
||||
* -1 if connect() fails and die()s for other errors.
|
||||
*/
|
||||
int ipc_connect_impl(const char *socket_path);
|
||||
|
||||
/**
|
||||
* Formats a message (payload) of the given size and type and sends it to i3 via
|
||||
* the given socket file descriptor.
|
||||
|
@ -50,12 +50,6 @@ void output_init_con(Output *output);
|
||||
*/
|
||||
void init_ws_for_output(Output *output);
|
||||
|
||||
/**
|
||||
* Initializes the specified output, assigning the specified workspace to it.
|
||||
*
|
||||
*/
|
||||
//void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
|
||||
|
||||
/**
|
||||
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
|
||||
*
|
||||
|
16
include/tiling_drag.h
Normal file
16
include/tiling_drag.h
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* tiling_drag.h: Reposition tiled windows by dragging.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Initiates a mouse drag operation on a tiled window.
|
||||
*
|
||||
*/
|
||||
void tiling_drag(Con *con, xcb_button_press_event_t *event);
|
@ -183,3 +183,15 @@ position_t position_from_direction(direction_t direction);
|
||||
*
|
||||
*/
|
||||
direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
|
||||
|
||||
/**
|
||||
* Converts direction to a string representation.
|
||||
*
|
||||
*/
|
||||
const char *direction_to_string(direction_t direction);
|
||||
|
||||
/**
|
||||
* Converts position to a string representation.
|
||||
*
|
||||
*/
|
||||
const char *position_to_string(position_t position);
|
||||
|
@ -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
|
||||
|
@ -92,7 +92,7 @@ Con *create_workspace_on_output(Output *output, Con *content);
|
||||
|
||||
/**
|
||||
* Returns true if the workspace is currently visible. Especially important for
|
||||
* multi-monitor environments, as they can have multiple currenlty active
|
||||
* multi-monitor environments, as they can have multiple currently active
|
||||
* workspaces.
|
||||
*
|
||||
*/
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -34,10 +35,20 @@ int create_socket(const char *filename, char **out_socketpath) {
|
||||
}
|
||||
free(copy);
|
||||
|
||||
/* Check if the socket is in use by another process (this call does not
|
||||
* succeed if the socket is stale / the owner already exited) */
|
||||
int sockfd = ipc_connect_impl(resolved);
|
||||
if (sockfd != -1) {
|
||||
ELOG("Refusing to create UNIX socket at %s: Socket is already in use\n", resolved);
|
||||
close(sockfd);
|
||||
errno = EEXIST;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Unlink the unix domain socket before */
|
||||
unlink(resolved);
|
||||
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) {
|
||||
perror("socket()");
|
||||
free(resolved);
|
||||
|
@ -160,7 +160,7 @@ i3Font load_font(const char *pattern, const bool fallback) {
|
||||
font.type = FONT_TYPE_NONE;
|
||||
font.pattern = NULL;
|
||||
|
||||
/* No XCB connction, return early because we're just validating the
|
||||
/* No XCB connection, return early because we're just validating the
|
||||
* configuration file. */
|
||||
if (conn == NULL) {
|
||||
return font;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* Connects to the i3 IPC socket and returns the file descriptor for the
|
||||
@ -39,6 +40,20 @@ int ipc_connect(const char *socket_path) {
|
||||
path = sstrdup("/tmp/i3-ipc.sock");
|
||||
}
|
||||
|
||||
int sockfd = ipc_connect_impl(path);
|
||||
if (sockfd < 0) {
|
||||
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
|
||||
}
|
||||
free(path);
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the socket at the given path with no fallback paths. Returns
|
||||
* -1 if connect() fails and die()s for other errors.
|
||||
*
|
||||
*/
|
||||
int ipc_connect_impl(const char *socket_path) {
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd == -1)
|
||||
err(EXIT_FAILURE, "Could not create socket");
|
||||
@ -48,9 +63,10 @@ int ipc_connect(const char *socket_path) {
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
|
||||
free(path);
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
close(sockfd);
|
||||
return -1;
|
||||
}
|
||||
return sockfd;
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -6,7 +6,7 @@
|
||||
project(
|
||||
'i3',
|
||||
'c',
|
||||
version: '4.20.1',
|
||||
version: '4.21',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=1', # enable all warnings (-Wall)
|
||||
@ -316,7 +316,7 @@ xcb_util_xrm_dep = dependency('xcb-xrm', method: 'pkg-config')
|
||||
xkbcommon_dep = dependency('xkbcommon', method: 'pkg-config')
|
||||
xkbcommon_x11_dep = dependency('xkbcommon-x11', method: 'pkg-config')
|
||||
yajl_dep = dependency('yajl', method: 'pkg-config')
|
||||
libpcre_dep = dependency('libpcre', version: '>=8.10', method: 'pkg-config')
|
||||
libpcre_dep = dependency('libpcre2-8', version: '>=10', method: 'pkg-config')
|
||||
cairo_dep = dependency('cairo', version: '>=1.14.4', method: 'pkg-config')
|
||||
pangocairo_dep = dependency('pangocairo', method: 'pkg-config')
|
||||
glib_dep = dependency('glib-2.0', method: 'pkg-config')
|
||||
@ -409,6 +409,7 @@ i3srcs = [
|
||||
'src/sighandler.c',
|
||||
'src/startup.c',
|
||||
'src/sync.c',
|
||||
'src/tiling_drag.c',
|
||||
'src/tree.c',
|
||||
'src/util.c',
|
||||
'src/version.c',
|
||||
|
@ -166,8 +166,10 @@ state FOCUS_AUTO:
|
||||
-> call cmd_focus_direction($direction)
|
||||
|
||||
state FOCUS_OUTPUT:
|
||||
output = string
|
||||
-> call cmd_focus_output($output)
|
||||
output = word
|
||||
-> call cmd_focus_output($output); FOCUS_OUTPUT
|
||||
end
|
||||
-> call cmd_focus_output(NULL); INITIAL
|
||||
|
||||
# kill [window|client]
|
||||
state KILL:
|
||||
@ -466,6 +468,8 @@ state TITLE_FORMAT:
|
||||
state TITLE_WINDOW_ICON:
|
||||
'padding'
|
||||
-> TITLE_WINDOW_ICON_PADDING
|
||||
enable = 'toggle'
|
||||
-> TITLE_WINDOW_ICON_PADDING
|
||||
enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
|
||||
-> call cmd_title_window_icon($enable, 0)
|
||||
|
||||
|
@ -56,7 +56,7 @@ state INITIAL:
|
||||
exectype = 'exec_always', 'exec' -> EXEC
|
||||
colorclass = 'client.background'
|
||||
-> COLOR_SINGLE
|
||||
colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
|
||||
colorclass = 'client.focused_inactive', 'client.focused_tab_title', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
|
||||
-> COLOR_BORDER
|
||||
|
||||
# We ignore comments and 'set' lines (variables).
|
||||
@ -400,8 +400,6 @@ state BINDCOMMAND:
|
||||
->
|
||||
command = string
|
||||
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
|
||||
end
|
||||
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
|
||||
|
||||
################################################################################
|
||||
# Mode configuration
|
||||
@ -567,7 +565,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:
|
||||
|
1
release-notes/bugfixes/0-example
Normal file
1
release-notes/bugfixes/0-example
Normal file
@ -0,0 +1 @@
|
||||
fix crash with "layout default"
|
1
release-notes/bugfixes/1-replace-socket
Normal file
1
release-notes/bugfixes/1-replace-socket
Normal file
@ -0,0 +1 @@
|
||||
Do not replace existing IPC socket on start
|
1
release-notes/bugfixes/2-fix-focus-wrap
Normal file
1
release-notes/bugfixes/2-fix-focus-wrap
Normal file
@ -0,0 +1 @@
|
||||
fix focus when moving container between outputs with mouse warp and focus_follows_mouse
|
1
release-notes/bugfixes/3-transient_for
Normal file
1
release-notes/bugfixes/3-transient_for
Normal file
@ -0,0 +1 @@
|
||||
Fix endless loop with transient_for windows
|
1
release-notes/bugfixes/4-failed-workspace-output
Normal file
1
release-notes/bugfixes/4-failed-workspace-output
Normal file
@ -0,0 +1 @@
|
||||
fix wrong failed reply on move workspace to output
|
1
release-notes/bugfixes/5-fix-wm-registration
Normal file
1
release-notes/bugfixes/5-fix-wm-registration
Normal file
@ -0,0 +1 @@
|
||||
changed WM registration selection from WM_S_S<screen> to WM_S<screen>
|
1
release-notes/bugfixes/6-fix-graphics-artifacts
Normal file
1
release-notes/bugfixes/6-fix-graphics-artifacts
Normal file
@ -0,0 +1 @@
|
||||
avoid graphics artifacts when changing the layout tree by initializing surfaces to all black
|
1
release-notes/bugfixes/7-fix-segfault
Normal file
1
release-notes/bugfixes/7-fix-segfault
Normal file
@ -0,0 +1 @@
|
||||
Fix segfault if command in bindsym is empty
|
@ -0,0 +1 @@
|
||||
update parent split con titles when child con swaps position with another child con
|
1
release-notes/bugfixes/8-bar-font
Normal file
1
release-notes/bugfixes/8-bar-font
Normal file
@ -0,0 +1 @@
|
||||
fix default font not being applied to bars if defined after bar block
|
1
release-notes/bugfixes/8-bar-output-trailing-whitespace
Normal file
1
release-notes/bugfixes/8-bar-output-trailing-whitespace
Normal file
@ -0,0 +1 @@
|
||||
strip trailing whitespace in bar output names
|
1
release-notes/bugfixes/8-fix-nested-variables-crash
Normal file
1
release-notes/bugfixes/8-fix-nested-variables-crash
Normal file
@ -0,0 +1 @@
|
||||
Fix crash if config contains nested variables.
|
1
release-notes/bugfixes/8-mode-default-sigsegv
Normal file
1
release-notes/bugfixes/8-mode-default-sigsegv
Normal file
@ -0,0 +1 @@
|
||||
Fix segfault with explicit mode "default" key bindings.
|
1
release-notes/bugfixes/9-bs-normal
Normal file
1
release-notes/bugfixes/9-bs-normal
Normal file
@ -0,0 +1 @@
|
||||
Restore BS_NORMAL _MOTIF_WM_HINTS correctly
|
1
release-notes/changes/0-example
Normal file
1
release-notes/changes/0-example
Normal file
@ -0,0 +1 @@
|
||||
Acquire the WM_Sn selection when starting as required by ICCCM
|
1
release-notes/changes/1-valid-socket
Normal file
1
release-notes/changes/1-valid-socket
Normal file
@ -0,0 +1 @@
|
||||
Refuse to start without valid IPC socket
|
1
release-notes/changes/2-client.focused_tab_title
Normal file
1
release-notes/changes/2-client.focused_tab_title
Normal file
@ -0,0 +1 @@
|
||||
Add client.focused_tab_title color option
|
1
release-notes/changes/3-focus-outputs
Normal file
1
release-notes/changes/3-focus-outputs
Normal file
@ -0,0 +1 @@
|
||||
Add support for multiple outputs in focus command
|
1
release-notes/changes/3-tiling-drag
Normal file
1
release-notes/changes/3-tiling-drag
Normal file
@ -0,0 +1 @@
|
||||
Allow moving tiling windows with the mouse
|
1
release-notes/changes/4-title_window_icon-toggle
Normal file
1
release-notes/changes/4-title_window_icon-toggle
Normal file
@ -0,0 +1 @@
|
||||
Add title_window_icon toggle
|
96
release-notes/generator.pl
Executable file
96
release-notes/generator.pl
Executable file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use v5.10;
|
||||
use Getopt::Long;
|
||||
|
||||
my @template = (
|
||||
'
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.21 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.21. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in i3 v4.21 │
|
||||
└────────────────────────────┘
|
||||
|
||||
',
|
||||
'
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
');
|
||||
|
||||
my $print_urls = 0;
|
||||
my $result = GetOptions('print-urls' => \$print_urls);
|
||||
|
||||
sub get_number {
|
||||
my $s = shift;
|
||||
return $1 if $s =~ m/^(\d+)/;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sub read_changefiles {
|
||||
my $dirpath = shift;
|
||||
opendir my $dir, $dirpath or die "Cannot open directory $dirpath: $!";
|
||||
my @files = sort { get_number($a) <=> get_number($b) } readdir $dir;
|
||||
|
||||
closedir $dir;
|
||||
|
||||
my $s = '';
|
||||
for my $filename (@files) {
|
||||
next if $filename eq '.';
|
||||
next if $filename eq '..';
|
||||
next if $filename eq '0-example';
|
||||
|
||||
die "Filename $filename should start with a number (e.g. the pull request number)" unless get_number($filename) > 0;
|
||||
|
||||
$filename = $dirpath . '/' . $filename;
|
||||
open my $in, '<', $filename or die "can't open $filename: $!";
|
||||
my @lines = <$in>;
|
||||
close $in or die "can't close $filename: $!";
|
||||
|
||||
my $content = trim(join("\n ", map { trim($_) } @lines));
|
||||
die "$filename can't be empty" unless length($content) > 0;
|
||||
|
||||
my $url = '';
|
||||
if ($print_urls) {
|
||||
my $commit = `git log --diff-filter=A --pretty=format:"%H" $filename`;
|
||||
$commit = trim($commit) if defined($commit);
|
||||
die "$filename: git log failed to find commit" if ($?) || (length($commit) == 0);
|
||||
|
||||
my $pr = find_pr($commit);
|
||||
$url = 'https://github.com/i3/i3/commit/' . $commit;
|
||||
$url = 'https://github.com/i3/i3/pull/' . $pr if defined($pr);
|
||||
$url = $url . "\n";
|
||||
}
|
||||
|
||||
$s = $s . ' • ' . $content . "\n" . $url;
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
sub find_pr {
|
||||
my $hash = shift;
|
||||
my $result = `git log --merges --ancestry-path --oneline $hash..next | grep 'Merge pull request' | tail -n1`;
|
||||
return unless defined($result);
|
||||
|
||||
return unless ($result =~ /Merge pull request .([0-9]+)/);
|
||||
return $1;
|
||||
}
|
||||
|
||||
sub trim {
|
||||
(my $s = $_[0]) =~ s/^\s+|\s+$//g;
|
||||
return $s;
|
||||
}
|
||||
|
||||
# Expected to run for i3's git root
|
||||
my $changes = read_changefiles('release-notes/changes');
|
||||
my $bugfixes = read_changefiles('release-notes/bugfixes');
|
||||
|
||||
print $template[0] . $changes . $template[1] . $bugfixes;
|
@ -22,7 +22,8 @@ fi
|
||||
|
||||
if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
|
||||
then
|
||||
echo "RELEASE-NOTES-${RELEASE_VERSION} not found."
|
||||
echo "RELEASE-NOTES-${RELEASE_VERSION} not found. Here is the output from the generator:"
|
||||
./release-notes/generator.pl --print-urls
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
34
src/click.c
34
src/click.c
@ -80,7 +80,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
|
||||
*/
|
||||
static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
|
||||
/* The client is in tiling layout. We can still initiate a resize with the
|
||||
* right mouse button, by chosing the border which is the most near one to
|
||||
* right mouse button, by choosing the border which is the most near one to
|
||||
* the position of the mouse pointer */
|
||||
int to_right = con->rect.width - event->event_x,
|
||||
to_left = event->event_x,
|
||||
@ -188,9 +188,6 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ws != focused_workspace)
|
||||
workspace_show(ws);
|
||||
|
||||
/* get the floating con */
|
||||
Con *floatingcon = con_inside_floating(con);
|
||||
const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT;
|
||||
@ -218,7 +215,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 2: focus this con or one of its children. */
|
||||
/* 2: floating modifier pressed, initiate a drag */
|
||||
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1 && !floatingcon) {
|
||||
tiling_drag(con, event);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 3: focus this con or one of its children. */
|
||||
Con *con_to_focus = con;
|
||||
if (in_stacked && dest == CLICK_DECORATION) {
|
||||
/* If the container is a tab/stacked container and the click happened
|
||||
@ -231,19 +234,22 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ws != focused_workspace) {
|
||||
workspace_show(ws);
|
||||
}
|
||||
con_activate(con_to_focus);
|
||||
|
||||
/* 3: For floating containers, we also want to raise them on click.
|
||||
/* 4: For floating containers, we also want to raise them on click.
|
||||
* We will skip handling events on floating cons in fullscreen mode */
|
||||
Con *fs = con_get_fullscreen_covering_ws(ws);
|
||||
if (floatingcon != NULL && fs != con) {
|
||||
/* 4: floating_modifier plus left mouse button drags */
|
||||
/* 5: floating_modifier plus left mouse button drags */
|
||||
if (mod_pressed && is_left_click) {
|
||||
floating_drag_window(floatingcon, event, false);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 5: resize (floating) if this was a (left or right) click on the
|
||||
/* 6: resize (floating) if this was a (left or right) click on the
|
||||
* left/right/bottom border, or a right click on the decoration.
|
||||
* also try resizing (tiling) if possible */
|
||||
if (mod_pressed && is_right_click) {
|
||||
@ -272,7 +278,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
return;
|
||||
}
|
||||
|
||||
/* 6: dragging, if this was a click on a decoration (which did not lead
|
||||
/* 7: dragging, if this was a click on a decoration (which did not lead
|
||||
* to a resize) */
|
||||
if (dest == CLICK_DECORATION && is_left_click) {
|
||||
floating_drag_window(floatingcon, event, !was_focused);
|
||||
@ -282,7 +288,13 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 7: floating modifier pressed, initiate a resize */
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 9: floating modifier pressed, initiate a resize */
|
||||
if (dest == CLICK_INSIDE && mod_pressed && is_right_click) {
|
||||
if (floating_mod_on_tiled_client(con, event)) {
|
||||
return;
|
||||
@ -293,7 +305,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
xcb_flush(conn);
|
||||
return;
|
||||
}
|
||||
/* 8: otherwise, check for border/decoration clicks and resize */
|
||||
/* 10: otherwise, check for border/decoration clicks and resize */
|
||||
if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
|
||||
is_left_or_right_click) {
|
||||
DLOG("Trying to resize (tiling)\n");
|
||||
|
214
src/commands.c
214
src/commands.c
@ -1023,6 +1023,76 @@ void cmd_mode(I3_CMD, const char *mode) {
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
typedef struct user_output_name {
|
||||
char *name;
|
||||
TAILQ_ENTRY(user_output_name) user_output_names;
|
||||
} 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. */
|
||||
Output *output;
|
||||
TAILQ_FOREACH (output, &outputs, outputs) {
|
||||
user_output_name *co = scalloc(sizeof(user_output_name), 1);
|
||||
co->name = sstrdup(output_primary_name(output));
|
||||
TAILQ_INSERT_TAIL(list, co, user_output_names);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
user_output_name *co = scalloc(sizeof(user_output_name), 1);
|
||||
co->name = sstrdup(name);
|
||||
TAILQ_INSERT_TAIL(list, co, user_output_names);
|
||||
return;
|
||||
}
|
||||
|
||||
static Output *user_output_names_find_next(user_output_names_head *names, Output *current_output) {
|
||||
Output *target_output = NULL;
|
||||
user_output_name *uo;
|
||||
TAILQ_FOREACH (uo, names, user_output_names) {
|
||||
if (!target_output) {
|
||||
/* The first available output from the list is used in 2 cases:
|
||||
* 1. When we must wrap around the user list. For example, if user
|
||||
* specifies outputs A B C and C is `current_output`.
|
||||
* 2. When the current output is not in the user list. For example,
|
||||
* user specifies A B C and D is `current_output`. */
|
||||
target_output = get_output_from_string(current_output, uo->name);
|
||||
}
|
||||
if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
|
||||
/* The current output is in the user list */
|
||||
while (true) {
|
||||
/* This corrupts the outer loop but it is ok since we are going
|
||||
* to break anyway. */
|
||||
uo = TAILQ_NEXT(uo, user_output_names);
|
||||
if (!uo) {
|
||||
/* We reached the end of the list. We should use the first
|
||||
* available output that, if it exists, is already saved in
|
||||
* target_output. */
|
||||
break;
|
||||
}
|
||||
Output *out = get_output_from_string(current_output, uo->name);
|
||||
if (out) {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return target_output;
|
||||
}
|
||||
|
||||
static void user_output_names_free(user_output_names_head *names) {
|
||||
user_output_name *uo;
|
||||
while (!TAILQ_EMPTY(names)) {
|
||||
uo = TAILQ_FIRST(names);
|
||||
free(uo->name);
|
||||
TAILQ_REMOVE(names, uo, user_output_names);
|
||||
free(uo);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'move [window|container|workspace] [to] output <strings>'.
|
||||
*
|
||||
@ -1031,40 +1101,21 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
|
||||
/* Initialize a data structure that is used to save multiple user-specified
|
||||
* output names since this function is called multiple types for each
|
||||
* command call. */
|
||||
typedef struct user_output_name {
|
||||
char *name;
|
||||
TAILQ_ENTRY(user_output_name) user_output_names;
|
||||
} user_output_name;
|
||||
static TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names = TAILQ_HEAD_INITIALIZER(user_output_names);
|
||||
static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
|
||||
|
||||
if (name) {
|
||||
if (strcmp(name, "next") == 0) {
|
||||
/* "next" here works like a wildcard: It "expands" to all available
|
||||
* outputs. */
|
||||
Output *output;
|
||||
TAILQ_FOREACH (output, &outputs, outputs) {
|
||||
user_output_name *co = scalloc(sizeof(user_output_name), 1);
|
||||
co->name = sstrdup(output_primary_name(output));
|
||||
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
user_output_name *co = scalloc(sizeof(user_output_name), 1);
|
||||
co->name = sstrdup(name);
|
||||
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
|
||||
user_output_names_add(&names, name);
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
if (TAILQ_EMPTY(&user_output_names)) {
|
||||
if (TAILQ_EMPTY(&names)) {
|
||||
yerror("At least one output must be specified");
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
user_output_name *uo;
|
||||
owindow *current;
|
||||
TAILQ_FOREACH (current, &owindows, owindows) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
@ -1073,41 +1124,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
|
||||
}
|
||||
|
||||
Output *current_output = get_output_for_con(ws);
|
||||
|
||||
Output *target_output = NULL;
|
||||
TAILQ_FOREACH (uo, &user_output_names, user_output_names) {
|
||||
if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
|
||||
/* The current output is in the user list */
|
||||
while (true) {
|
||||
/* This corrupts the outer loop but it is ok since we are
|
||||
* going to break anyway. */
|
||||
uo = TAILQ_NEXT(uo, user_output_names);
|
||||
if (!uo) {
|
||||
/* We reached the end of the list. We should use the
|
||||
* first available output that, if it exists, is
|
||||
* already saved in target_output. */
|
||||
break;
|
||||
}
|
||||
Output *out = get_output_from_string(current_output, uo->name);
|
||||
if (out) {
|
||||
DLOG("Found next target for workspace %s from user list: %s\n", ws->name, uo->name);
|
||||
target_output = out;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!target_output) {
|
||||
/* The first available output from the list is used in 2 cases:
|
||||
* 1. When we must wrap around the user list. For example, if
|
||||
* user specifies outputs A B C and C is `current_output`.
|
||||
* 2. When the current output is not in the user list. For
|
||||
* example, user specifies A B C and D is `current_output`.
|
||||
*/
|
||||
DLOG("Found first target for workspace %s from user list: %s\n", ws->name, uo->name);
|
||||
target_output = get_output_from_string(current_output, uo->name);
|
||||
}
|
||||
}
|
||||
Output *target_output = user_output_names_find_next(&names, current_output);
|
||||
if (target_output) {
|
||||
if (move_workspace) {
|
||||
workspace_move_to_output(ws, target_output);
|
||||
@ -1117,13 +1134,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (!TAILQ_EMPTY(&user_output_names)) {
|
||||
uo = TAILQ_FIRST(&user_output_names);
|
||||
free(uo->name);
|
||||
TAILQ_REMOVE(&user_output_names, uo, user_output_names);
|
||||
free(uo);
|
||||
}
|
||||
user_output_names_free(&names);
|
||||
|
||||
cmd_output->needs_tree_render = success;
|
||||
if (success) {
|
||||
@ -1757,6 +1768,17 @@ void cmd_open(I3_CMD) {
|
||||
*
|
||||
*/
|
||||
void cmd_focus_output(I3_CMD, const char *name) {
|
||||
static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
|
||||
if (name) {
|
||||
user_output_names_add(&names, name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TAILQ_EMPTY(&names)) {
|
||||
yerror("At least one output must be specified");
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
if (TAILQ_EMPTY(&owindows)) {
|
||||
@ -1765,25 +1787,29 @@ void cmd_focus_output(I3_CMD, const char *name) {
|
||||
}
|
||||
|
||||
Output *current_output = get_output_for_con(TAILQ_FIRST(&owindows)->con);
|
||||
Output *output = get_output_from_string(current_output, name);
|
||||
Output *target_output = user_output_names_find_next(&names, current_output);
|
||||
user_output_names_free(&names);
|
||||
bool success = false;
|
||||
if (target_output) {
|
||||
success = true;
|
||||
|
||||
if (!output) {
|
||||
yerror("Output %s not found.", name);
|
||||
return;
|
||||
/* get visible workspace on output */
|
||||
Con *ws = NULL;
|
||||
GREP_FIRST(ws, output_get_content(target_output->con), workspace_is_visible(child));
|
||||
if (!ws) {
|
||||
yerror("BUG: No workspace found on output.");
|
||||
return;
|
||||
}
|
||||
|
||||
workspace_show(ws);
|
||||
}
|
||||
|
||||
/* get visible workspace on output */
|
||||
Con *ws = NULL;
|
||||
GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
|
||||
if (!ws) {
|
||||
yerror("BUG: No workspace found on output.");
|
||||
return;
|
||||
cmd_output->needs_tree_render = success;
|
||||
if (success) {
|
||||
ysuccess(true);
|
||||
} else {
|
||||
yerror("No output matched");
|
||||
}
|
||||
|
||||
workspace_show(ws);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2038,20 +2064,40 @@ void cmd_title_format(I3_CMD, const char *format) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
|
||||
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon padding <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
|
||||
if (enable != NULL && !boolstr(enable)) {
|
||||
padding = -1;
|
||||
bool is_toggle = false;
|
||||
if (enable != NULL) {
|
||||
if (strcmp(enable, "toggle") == 0) {
|
||||
is_toggle = true;
|
||||
} else if (!boolstr(enable)) {
|
||||
padding = -1;
|
||||
}
|
||||
}
|
||||
DLOG("setting window_icon=%d\n", padding);
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
owindow *current;
|
||||
TAILQ_FOREACH (current, &owindows, owindows) {
|
||||
DLOG("setting window_icon for %p / %s\n", current->con, current->con->name);
|
||||
current->con->window_icon_padding = padding;
|
||||
if (is_toggle) {
|
||||
const int current_padding = current->con->window_icon_padding;
|
||||
if (padding > 0) {
|
||||
if (current_padding < 0) {
|
||||
current->con->window_icon_padding = padding;
|
||||
} else {
|
||||
/* toggle off, but store padding given */
|
||||
current->con->window_icon_padding = -(padding + 1);
|
||||
}
|
||||
} else {
|
||||
/* Set to negative of (current value+1) to keep old padding when toggling */
|
||||
current->con->window_icon_padding = -(current_padding + 1);
|
||||
}
|
||||
} else {
|
||||
current->con->window_icon_padding = padding;
|
||||
}
|
||||
DLOG("Set window_icon for %p / %s to %d\n", current->con, current->con->name, current->con->window_icon_padding);
|
||||
|
||||
if (current->con->window != NULL) {
|
||||
/* Make sure the window title is redrawn immediately. */
|
||||
|
@ -390,7 +390,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
|
||||
free(possible_tokens);
|
||||
|
||||
/* Contains the same amount of characters as 'input' has, but with
|
||||
* the unparseable part highlighted using ^ characters. */
|
||||
* the unparsable part highlighted using ^ characters. */
|
||||
char *position = smalloc(len + 1);
|
||||
for (const char *copywalk = input; *copywalk != '\0'; copywalk++)
|
||||
position[(copywalk - input)] = (copywalk >= walk ? '^' : ' ');
|
||||
|
76
src/con.c
76
src/con.c
@ -730,6 +730,41 @@ Con *con_by_mark(const char *mark) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start from a container and traverse the transient_for linked list. Returns
|
||||
* true if target window is found in the list. Protects againsts potential
|
||||
* cycles.
|
||||
*
|
||||
*/
|
||||
bool con_find_transient_for_window(Con *start, xcb_window_t target) {
|
||||
Con *transient_con = start;
|
||||
int count = con_num_windows(croot);
|
||||
while (transient_con != NULL &&
|
||||
transient_con->window != NULL &&
|
||||
transient_con->window->transient_for != XCB_NONE) {
|
||||
DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, target = 0x%08x\n",
|
||||
transient_con->window->id, transient_con->window->transient_for, target);
|
||||
if (transient_con->window->transient_for == target) {
|
||||
return true;
|
||||
}
|
||||
Con *next_transient = con_by_window_id(transient_con->window->transient_for);
|
||||
if (next_transient == NULL) {
|
||||
break;
|
||||
}
|
||||
/* Some clients (e.g. x11-ssh-askpass) actually set WM_TRANSIENT_FOR to
|
||||
* their own window id, so break instead of looping endlessly. */
|
||||
if (transient_con == next_transient) {
|
||||
break;
|
||||
}
|
||||
transient_con = next_transient;
|
||||
|
||||
if (count-- <= 0) { /* Avoid cycles, see #4404 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if and only if the given containers holds the mark.
|
||||
*
|
||||
@ -852,8 +887,6 @@ void con_unmark(Con *con, const char *name) {
|
||||
Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
|
||||
Con *child;
|
||||
Match *match;
|
||||
//DLOG("searching con for window %p starting at con %p\n", window, con);
|
||||
//DLOG("class == %s\n", window->class_class);
|
||||
|
||||
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
|
||||
TAILQ_FOREACH (match, &(child->swallow_head), matches) {
|
||||
@ -1012,8 +1045,8 @@ void con_fix_percent(Con *con) {
|
||||
Con *child;
|
||||
int children = con_num_children(con);
|
||||
|
||||
// calculate how much we have distributed and how many containers
|
||||
// with a percentage set we have
|
||||
/* calculate how much we have distributed and how many containers with a
|
||||
* percentage set we have */
|
||||
double total = 0.0;
|
||||
int children_with_percent = 0;
|
||||
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
|
||||
@ -1023,8 +1056,8 @@ void con_fix_percent(Con *con) {
|
||||
}
|
||||
}
|
||||
|
||||
// if there were children without a percentage set, set to a value that
|
||||
// will make those children proportional to all others
|
||||
/* if there were children without a percentage set, set to a value that
|
||||
* will make those children proportional to all others */
|
||||
if (children_with_percent != children) {
|
||||
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
|
||||
if (child->percent <= 0.0) {
|
||||
@ -1037,8 +1070,8 @@ void con_fix_percent(Con *con) {
|
||||
}
|
||||
}
|
||||
|
||||
// if we got a zero, just distribute the space equally, otherwise
|
||||
// distribute according to the proportions we got
|
||||
/* if we got a zero, just distribute the space equally, otherwise
|
||||
* distribute according to the proportions we got */
|
||||
if (total == 0.0) {
|
||||
TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
|
||||
child->percent = 1.0 / children;
|
||||
@ -1356,17 +1389,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the given container to the given mark.
|
||||
*
|
||||
*/
|
||||
bool con_move_to_mark(Con *con, const char *mark) {
|
||||
Con *target = con_by_mark(mark);
|
||||
if (target == NULL) {
|
||||
DLOG("found no container with mark \"%s\"\n", mark);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool con_move_to_target(Con *con, Con *target) {
|
||||
/* For target containers in the scratchpad, we just send the window to the scratchpad. */
|
||||
if (con_get_workspace(target) == workspace_get("__i3_scratch")) {
|
||||
DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
|
||||
@ -1403,6 +1426,20 @@ bool con_move_to_mark(Con *con, const char *mark) {
|
||||
return _con_move_to_con(con, target, false, true, false, false, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the given container to the given mark.
|
||||
*
|
||||
*/
|
||||
bool con_move_to_mark(Con *con, const char *mark) {
|
||||
Con *target = con_by_mark(mark);
|
||||
if (target == NULL) {
|
||||
DLOG("found no container with mark \"%s\"\n", mark);
|
||||
return false;
|
||||
}
|
||||
|
||||
return con_move_to_target(con, target);
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the given container to the currently focused container on the given
|
||||
* workspace.
|
||||
@ -2202,7 +2239,6 @@ void con_set_urgency(Con *con, bool urgent) {
|
||||
} else
|
||||
DLOG("Discarding urgency WM_HINT because timer is running\n");
|
||||
|
||||
//CLIENT_LOG(con);
|
||||
if (con->window) {
|
||||
if (con->urgent) {
|
||||
gettimeofday(&con->window->urgent, NULL);
|
||||
|
10
src/config.c
10
src/config.c
@ -197,6 +197,7 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
|
||||
INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50");
|
||||
INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e");
|
||||
INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff", "#900000");
|
||||
config.client.got_focused_tab_title = false;
|
||||
|
||||
/* border and indicator color are ignored for placeholder contents */
|
||||
INIT_COLOR(config.client.placeholder, "#000000", "#0c0c0c", "#ffffff", "#000000");
|
||||
@ -272,6 +273,15 @@ 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;
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
@ -467,24 +466,32 @@ CFGFUN(color_single, const char *colorclass, const char *color) {
|
||||
}
|
||||
|
||||
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border) {
|
||||
#define APPLY_COLORS(classname) \
|
||||
do { \
|
||||
if (strcmp(colorclass, "client." #classname) == 0) { \
|
||||
config.client.classname.border = draw_util_hex_to_color(border); \
|
||||
config.client.classname.background = draw_util_hex_to_color(background); \
|
||||
config.client.classname.text = draw_util_hex_to_color(text); \
|
||||
if (indicator != NULL) { \
|
||||
config.client.classname.indicator = draw_util_hex_to_color(indicator); \
|
||||
} \
|
||||
if (child_border != NULL) { \
|
||||
config.client.classname.child_border = draw_util_hex_to_color(child_border); \
|
||||
} else { \
|
||||
config.client.classname.child_border = config.client.classname.background; \
|
||||
} \
|
||||
} \
|
||||
#define APPLY_COLORS(classname) \
|
||||
do { \
|
||||
if (strcmp(colorclass, "client." #classname) == 0) { \
|
||||
if (strcmp("focused_tab_title", #classname) == 0) { \
|
||||
config.client.got_focused_tab_title = true; \
|
||||
if (indicator || child_border) { \
|
||||
ELOG("indicator and child_border colors have no effect for client.focused_tab_title\n"); \
|
||||
} \
|
||||
} \
|
||||
config.client.classname.border = draw_util_hex_to_color(border); \
|
||||
config.client.classname.background = draw_util_hex_to_color(background); \
|
||||
config.client.classname.text = draw_util_hex_to_color(text); \
|
||||
if (indicator != NULL) { \
|
||||
config.client.classname.indicator = draw_util_hex_to_color(indicator); \
|
||||
} \
|
||||
if (child_border != NULL) { \
|
||||
config.client.classname.child_border = draw_util_hex_to_color(child_border); \
|
||||
} else { \
|
||||
config.client.classname.child_border = config.client.classname.background; \
|
||||
} \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
APPLY_COLORS(focused_inactive);
|
||||
APPLY_COLORS(focused_tab_title);
|
||||
APPLY_COLORS(focused);
|
||||
APPLY_COLORS(unfocused);
|
||||
APPLY_COLORS(urgent);
|
||||
@ -744,10 +751,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;
|
||||
|
@ -165,8 +165,6 @@ static void clear_stack(struct stack *ctx) {
|
||||
static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
|
||||
cmdp_state _next_state = token->next_state;
|
||||
|
||||
//printf("token = name %s identifier %s\n", token->name, token->identifier);
|
||||
//printf("next_state = %d\n", token->next_state);
|
||||
if (token->next_state == __CALL) {
|
||||
struct ConfigResultIR subcommand_output = {
|
||||
.ctx = ctx,
|
||||
@ -254,7 +252,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
|
||||
bool token_handled;
|
||||
linecnt = 1;
|
||||
|
||||
// TODO: make this testable
|
||||
#ifndef TEST_PARSER
|
||||
struct ConfigResultIR subcommand_output = {
|
||||
.ctx = ctx,
|
||||
@ -270,8 +267,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
|
||||
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
|
||||
walk++;
|
||||
|
||||
//printf("remaining input: %s\n", walk);
|
||||
|
||||
cmdp_token_ptr *ptr = &(tokens[ctx->state]);
|
||||
token_handled = false;
|
||||
for (c = 0; c < ptr->n; c++) {
|
||||
@ -378,7 +373,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
|
||||
}
|
||||
|
||||
if (strcmp(token->name, "end") == 0) {
|
||||
//printf("checking for end: *%s*\n", walk);
|
||||
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
|
||||
next_state(token, ctx);
|
||||
token_handled = true;
|
||||
@ -386,7 +380,6 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
|
||||
* datastructure for commands which do *not* specify any
|
||||
* criteria, we re-initialize the criteria system after
|
||||
* every command. */
|
||||
// TODO: make this testable
|
||||
#ifndef TEST_PARSER
|
||||
cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
|
||||
#endif
|
||||
@ -445,7 +438,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
|
||||
const char *error_line = start_of_line(walk, input);
|
||||
|
||||
/* Contains the same amount of characters as 'input' has, but with
|
||||
* the unparseable part highlighted using ^ characters. */
|
||||
* the unparsable part highlighted using ^ characters. */
|
||||
char *position = scalloc(strlen(error_line) + 1, 1);
|
||||
const char *copywalk;
|
||||
for (copywalk = error_line;
|
||||
@ -1008,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;
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ static void print_config_path(const char *path, const char *role) {
|
||||
/*
|
||||
* Connects to i3 to find out the currently running version. Useful since it
|
||||
* might be different from the version compiled into this binary (maybe the
|
||||
* user didn’t correctly install i3 or forgot te restart it).
|
||||
* user didn’t correctly install i3 or forgot to restart it).
|
||||
*
|
||||
* The output looks like this:
|
||||
* Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
|
||||
|
@ -253,7 +253,7 @@ bool floating_enable(Con *con, bool automatic) {
|
||||
}
|
||||
/* Consider the part of the focus stack of our current workspace:
|
||||
* [ ... S_{i-1} S_{i} S_{i+1} ... ]
|
||||
* Where S_{x} is a container tree and the container 'con' that is beeing switched to
|
||||
* Where S_{x} is a container tree and the container 'con' that is being switched to
|
||||
* floating belongs in S_{i}. The new floating container, 'nc', will have the
|
||||
* workspace as its parent so it needs to be placed in this stack. If C was focused
|
||||
* we just need to call con_focus(). Otherwise, nc must be placed before or after S_{i}.
|
||||
|
@ -70,11 +70,9 @@ bool event_is_ignored(const int sequence, const int response_type) {
|
||||
event->response_type != response_type)
|
||||
continue;
|
||||
|
||||
/* instead of removing a sequence number we better wait until it gets
|
||||
* garbage collected. it may generate multiple events (there are multiple
|
||||
* enter_notifies for one configure_request, for example). */
|
||||
//SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
|
||||
//free(event);
|
||||
/* Instead of removing & freeing a sequence number we better wait until
|
||||
* it gets garbage collected. It may generate multiple events (there
|
||||
* are multiple enter_notifies for one configure_request, for example). */
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -270,7 +268,7 @@ static void handle_map_request(xcb_map_request_event_t *event) {
|
||||
* Configure requests are received when the application wants to resize windows
|
||||
* on their own.
|
||||
*
|
||||
* We generate a synthethic configure notify event to signalize the client its
|
||||
* We generate a synthetic configure notify event to signalize the client its
|
||||
* "new" position.
|
||||
*
|
||||
*/
|
||||
@ -801,7 +799,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
} else if (event->type == A_WM_CHANGE_STATE) {
|
||||
/* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */
|
||||
if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
|
||||
/* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
|
||||
/* For compatibility reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
|
||||
* immediately revert to normal to avoid being stuck in a paused state. */
|
||||
DLOG("Client has requested iconic state, rejecting. (window = %08x)\n", event->window);
|
||||
long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
|
||||
@ -1184,14 +1182,14 @@ static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
|
||||
* Handles the _MOTIF_WM_HINTS property of specifying window deocration settings.
|
||||
*
|
||||
*/
|
||||
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);
|
||||
|
||||
@ -1346,7 +1344,7 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
|
||||
}
|
||||
|
||||
if (handler == NULL) {
|
||||
//DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
|
||||
/* DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom); */
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1524,7 +1522,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
|
||||
break;
|
||||
|
||||
default:
|
||||
//DLOG("Unhandled event of type %d\n", type);
|
||||
/* DLOG("Unhandled event of type %d\n", type); */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
22
src/main.c
22
src/main.c
@ -683,6 +683,11 @@ int main(int argc, char *argv[]) {
|
||||
else
|
||||
config.ipc_socket_path = sstrdup(config.ipc_socket_path);
|
||||
}
|
||||
/* Create the UNIX domain socket for IPC */
|
||||
int ipc_socket = create_socket(config.ipc_socket_path, ¤t_socketpath);
|
||||
if (ipc_socket == -1) {
|
||||
die("Could not create the IPC socket: %s", config.ipc_socket_path);
|
||||
}
|
||||
|
||||
if (config.force_xinerama) {
|
||||
force_xinerama = true;
|
||||
@ -691,11 +696,11 @@ int main(int argc, char *argv[]) {
|
||||
/* Acquire the WM_Sn selection. */
|
||||
{
|
||||
/* Get the WM_Sn atom */
|
||||
char *atom_name = xcb_atom_name_by_screen("WM_S", conn_screen);
|
||||
char *atom_name = xcb_atom_name_by_screen("WM", conn_screen);
|
||||
wm_sn_selection_owner = xcb_generate_id(conn);
|
||||
|
||||
if (atom_name == NULL) {
|
||||
ELOG("xcb_atom_name_by_screen(\"WM_S\", %d) failed, exiting\n", conn_screen);
|
||||
ELOG("xcb_atom_name_by_screen(\"WM\", %d) failed, exiting\n", conn_screen);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -988,15 +993,10 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
tree_render();
|
||||
|
||||
/* Create the UNIX domain socket for IPC */
|
||||
int ipc_socket = create_socket(config.ipc_socket_path, ¤t_socketpath);
|
||||
if (ipc_socket == -1) {
|
||||
ELOG("Could not create the IPC socket, IPC disabled\n");
|
||||
} else {
|
||||
struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
|
||||
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
|
||||
ev_io_start(main_loop, ipc_io);
|
||||
}
|
||||
/* Listen to the IPC socket for clients */
|
||||
struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
|
||||
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
|
||||
ev_io_start(main_loop, ipc_io);
|
||||
|
||||
/* Chose a file name in /tmp/ based on the PID */
|
||||
char *log_stream_socket_path = get_process_filename("log-stream-socket");
|
||||
|
31
src/manage.c
31
src/manage.c
@ -123,7 +123,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
|
||||
geomc = xcb_get_geometry(conn, d);
|
||||
|
||||
/* Check if the window is mapped (it could be not mapped when intializing and
|
||||
/* Check if the window is mapped (it could be not mapped when initializing and
|
||||
calling manage_window() for every window) */
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
|
||||
DLOG("Could not get attributes\n");
|
||||
@ -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);
|
||||
@ -485,34 +485,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
(cwindow->leader != XCB_NONE &&
|
||||
cwindow->leader != cwindow->id &&
|
||||
con_by_window_id(cwindow->leader) != NULL)) {
|
||||
LOG("This window is transient for another window, setting floating\n");
|
||||
DLOG("This window is transient for another window, setting floating\n");
|
||||
want_floating = true;
|
||||
|
||||
if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN &&
|
||||
fs != NULL) {
|
||||
LOG("There is a fullscreen window, leaving fullscreen mode\n");
|
||||
DLOG("There is a fullscreen window, leaving fullscreen mode\n");
|
||||
con_toggle_fullscreen(fs, CF_OUTPUT);
|
||||
} else if (config.popup_during_fullscreen == PDF_SMART &&
|
||||
fs != NULL &&
|
||||
fs->window != NULL) {
|
||||
i3Window *transient_win = cwindow;
|
||||
while (transient_win != NULL &&
|
||||
transient_win->transient_for != XCB_NONE) {
|
||||
if (transient_win->transient_for == fs->window->id) {
|
||||
LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
|
||||
set_focus = true;
|
||||
break;
|
||||
}
|
||||
Con *next_transient = con_by_window_id(transient_win->transient_for);
|
||||
if (next_transient == NULL)
|
||||
break;
|
||||
/* Some clients (e.g. x11-ssh-askpass) actually set
|
||||
* WM_TRANSIENT_FOR to their own window id, so break instead of
|
||||
* looping endlessly. */
|
||||
if (transient_win == next_transient->window)
|
||||
break;
|
||||
transient_win = next_transient->window;
|
||||
}
|
||||
set_focus = con_find_transient_for_window(nc, fs->window->id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -528,7 +511,7 @@ 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 (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);
|
||||
|
16
src/move.c
16
src/move.c
@ -228,10 +228,19 @@ static void move_to_output_directed(Con *con, direction_t direction) {
|
||||
* the focused container, con, is now a child of ws. To work around this
|
||||
* and still produce the correct workspace focus events (see
|
||||
* 517-regress-move-direction-ipc.t) we need to temporarily set focused
|
||||
* to the old workspace. */
|
||||
* to the old workspace.
|
||||
*
|
||||
* The following happen:
|
||||
* 1. Focus con to push it on the top of the focus stack in its new
|
||||
* workspace
|
||||
* 2. Set focused to the old workspace to force workspace_show to
|
||||
* execute
|
||||
* 3. workspace_show will descend focus and target our con for
|
||||
* focusing. This also ensures that the mouse warps correctly.
|
||||
* See: #3518. */
|
||||
con_focus(con);
|
||||
focused = old_ws;
|
||||
workspace_show(ws);
|
||||
con_focus(con);
|
||||
}
|
||||
|
||||
/* force re-painting the indicators */
|
||||
@ -321,6 +330,9 @@ void tree_move(Con *con, direction_t direction) {
|
||||
TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
|
||||
}
|
||||
|
||||
/* redraw parents to ensure all parent split container titles are updated correctly */
|
||||
con_force_split_parents_redraw(con);
|
||||
|
||||
ipc_send_window_event("move", con);
|
||||
return;
|
||||
}
|
||||
|
26
src/randr.c
26
src/randr.c
@ -665,11 +665,12 @@ static bool randr_query_outputs_15(void) {
|
||||
|
||||
new->primary = monitor_info->primary;
|
||||
|
||||
new->changed =
|
||||
update_if_necessary(&(new->rect.x), monitor_info->x) |
|
||||
update_if_necessary(&(new->rect.y), monitor_info->y) |
|
||||
update_if_necessary(&(new->rect.width), monitor_info->width) |
|
||||
update_if_necessary(&(new->rect.height), monitor_info->height);
|
||||
const bool update_x = update_if_necessary(&(new->rect.x), monitor_info->x);
|
||||
const bool update_y = update_if_necessary(&(new->rect.y), monitor_info->y);
|
||||
const bool update_w = update_if_necessary(&(new->rect.width), monitor_info->width);
|
||||
const bool update_h = update_if_necessary(&(new->rect.height), monitor_info->height);
|
||||
|
||||
new->changed = update_x || update_y || update_w || update_h;
|
||||
|
||||
DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n",
|
||||
name,
|
||||
@ -743,10 +744,11 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
||||
return;
|
||||
}
|
||||
|
||||
bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
|
||||
update_if_necessary(&(new->rect.y), crtc->y) |
|
||||
update_if_necessary(&(new->rect.width), crtc->width) |
|
||||
update_if_necessary(&(new->rect.height), crtc->height);
|
||||
const bool update_x = update_if_necessary(&(new->rect.x), crtc->x);
|
||||
const bool update_y = update_if_necessary(&(new->rect.y), crtc->y);
|
||||
const bool update_w = update_if_necessary(&(new->rect.width), crtc->width);
|
||||
const bool update_h = update_if_necessary(&(new->rect.height), crtc->height);
|
||||
const bool updated = update_x || update_y || update_w || update_h;
|
||||
free(crtc);
|
||||
new->active = (new->rect.width != 0 && new->rect.height != 0);
|
||||
if (!new->active) {
|
||||
@ -943,9 +945,11 @@ void randr_query_outputs(void) {
|
||||
uint32_t width = min(other->rect.width, output->rect.width);
|
||||
uint32_t height = min(other->rect.height, output->rect.height);
|
||||
|
||||
if (update_if_necessary(&(output->rect.width), width) |
|
||||
update_if_necessary(&(output->rect.height), height))
|
||||
const bool update_w = update_if_necessary(&(output->rect.width), width);
|
||||
const bool update_h = update_if_necessary(&(output->rect.height), height);
|
||||
if (update_w || update_h) {
|
||||
output->changed = true;
|
||||
}
|
||||
|
||||
update_if_necessary(&(other->rect.width), width);
|
||||
update_if_necessary(&(other->rect.height), height);
|
||||
|
39
src/regex.c
39
src/regex.c
@ -20,34 +20,23 @@
|
||||
*
|
||||
*/
|
||||
struct regex *regex_new(const char *pattern) {
|
||||
const char *error;
|
||||
int errorcode, offset;
|
||||
int errorcode;
|
||||
PCRE2_SIZE offset;
|
||||
|
||||
struct regex *re = scalloc(1, sizeof(struct regex));
|
||||
re->pattern = sstrdup(pattern);
|
||||
int options = PCRE_UTF8;
|
||||
uint32_t options = PCRE2_UTF;
|
||||
/* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
|
||||
* character classes play nicely with Unicode */
|
||||
options |= PCRE_UCP;
|
||||
while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
|
||||
/* If the error is that PCRE was not compiled with UTF-8 support we
|
||||
* disable it and try again */
|
||||
if (errorcode == 32) {
|
||||
options &= ~PCRE_UTF8;
|
||||
continue;
|
||||
}
|
||||
ELOG("PCRE regular expression compilation failed at %d: %s\n",
|
||||
offset, error);
|
||||
options |= PCRE2_UCP;
|
||||
if (!(re->regex = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options, &errorcode, &offset, NULL))) {
|
||||
PCRE2_UCHAR buffer[256];
|
||||
pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
|
||||
ELOG("PCRE regular expression compilation failed at %lu: %s\n",
|
||||
offset, buffer);
|
||||
regex_free(re);
|
||||
return NULL;
|
||||
}
|
||||
re->extra = pcre_study(re->regex, 0, &error);
|
||||
/* If an error happened, we print the error message, but continue.
|
||||
* Studying the regular expression leads to faster matching, but it’s not
|
||||
* absolutely necessary. */
|
||||
if (error) {
|
||||
ELOG("PCRE regular expression studying failed: %s\n", error);
|
||||
}
|
||||
return re;
|
||||
}
|
||||
|
||||
@ -60,7 +49,6 @@ void regex_free(struct regex *regex) {
|
||||
return;
|
||||
FREE(regex->pattern);
|
||||
FREE(regex->regex);
|
||||
FREE(regex->extra);
|
||||
FREE(regex);
|
||||
}
|
||||
|
||||
@ -71,17 +59,22 @@ void regex_free(struct regex *regex) {
|
||||
*
|
||||
*/
|
||||
bool regex_matches(struct regex *regex, const char *input) {
|
||||
pcre2_match_data *match_data;
|
||||
int rc;
|
||||
|
||||
match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL);
|
||||
|
||||
/* We use strlen() because pcre_exec() expects the length of the input
|
||||
* string in bytes */
|
||||
if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
|
||||
rc = pcre2_match(regex->regex, (PCRE2_SPTR)input, strlen(input), 0, 0, match_data, NULL);
|
||||
pcre2_match_data_free(match_data);
|
||||
if (rc > 0) {
|
||||
LOG("Regular expression \"%s\" matches \"%s\"\n",
|
||||
regex->pattern, input);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rc == PCRE_ERROR_NOMATCH) {
|
||||
if (rc == PCRE2_ERROR_NOMATCH) {
|
||||
LOG("Regular expression \"%s\" does not match \"%s\"\n",
|
||||
regex->pattern, input);
|
||||
return false;
|
||||
|
28
src/render.c
28
src/render.c
@ -226,34 +226,12 @@ static void render_root(Con *con, Con *fullscreen) {
|
||||
}
|
||||
|
||||
Con *floating_child = con_descend_focused(child);
|
||||
Con *transient_con = floating_child;
|
||||
bool is_transient_for = false;
|
||||
while (transient_con != NULL &&
|
||||
transient_con->window != NULL &&
|
||||
transient_con->window->transient_for != XCB_NONE) {
|
||||
DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n",
|
||||
transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id);
|
||||
if (transient_con->window->transient_for == fullscreen->window->id) {
|
||||
is_transient_for = true;
|
||||
break;
|
||||
}
|
||||
Con *next_transient = con_by_window_id(transient_con->window->transient_for);
|
||||
if (next_transient == NULL)
|
||||
break;
|
||||
/* Some clients (e.g. x11-ssh-askpass) actually set
|
||||
* WM_TRANSIENT_FOR to their own window id, so break instead of
|
||||
* looping endlessly. */
|
||||
if (transient_con == next_transient)
|
||||
break;
|
||||
transient_con = next_transient;
|
||||
}
|
||||
|
||||
if (!is_transient_for)
|
||||
continue;
|
||||
else {
|
||||
if (con_find_transient_for_window(floating_child, fullscreen->window->id)) {
|
||||
DLOG("Rendering floating child even though in fullscreen mode: "
|
||||
"floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
|
||||
floating_child->window->transient_for, fullscreen->window->id);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
DLOG("floating child at (%d,%d) with %d x %d\n",
|
||||
|
385
src/tiling_drag.c
Normal file
385
src/tiling_drag.c
Normal file
@ -0,0 +1,385 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* tiling_drag.c: Reposition tiled windows by dragging.
|
||||
*
|
||||
*/
|
||||
#include "all.h"
|
||||
static xcb_window_t create_drop_indicator(Rect rect);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
static Rect con_rect_plus_deco_height(Con *con) {
|
||||
Rect rect = con->rect;
|
||||
rect.height += con->deco_rect.height;
|
||||
if (rect.y < con->deco_rect.height) {
|
||||
rect.y = 0;
|
||||
} else {
|
||||
rect.y -= con->deco_rect.height;
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an appropriate target at given coordinates.
|
||||
*
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Couldn't find leaf container, get a workspace. */
|
||||
Output *output = get_output_containing(x, y);
|
||||
if (!output) {
|
||||
return NULL;
|
||||
}
|
||||
Con *content = output_get_content(output->con);
|
||||
/* Still descend because you can drag to the bar on an non-empty workspace. */
|
||||
return con_descend_tiling_focused(content);
|
||||
}
|
||||
|
||||
typedef enum { DT_SIBLING,
|
||||
DT_CENTER,
|
||||
DT_PARENT
|
||||
} drop_type_t;
|
||||
|
||||
struct callback_params {
|
||||
xcb_window_t *indicator;
|
||||
Con **target;
|
||||
direction_t *direction;
|
||||
drop_type_t *drop_type;
|
||||
};
|
||||
|
||||
static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold) {
|
||||
switch (direction) {
|
||||
case D_LEFT:
|
||||
rect.width = threshold;
|
||||
break;
|
||||
case D_UP:
|
||||
rect.height = threshold;
|
||||
break;
|
||||
case D_RIGHT:
|
||||
rect.x += (rect.width - threshold);
|
||||
rect.width = threshold;
|
||||
break;
|
||||
case D_DOWN:
|
||||
rect.y += (rect.height - threshold);
|
||||
rect.height = threshold;
|
||||
break;
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
static bool con_on_side_of_parent(Con *con, direction_t direction) {
|
||||
const orientation_t orientation = orientation_from_direction(direction);
|
||||
direction_t reverse_direction;
|
||||
switch (direction) {
|
||||
case D_LEFT:
|
||||
reverse_direction = D_RIGHT;
|
||||
break;
|
||||
case D_RIGHT:
|
||||
reverse_direction = D_LEFT;
|
||||
break;
|
||||
case D_UP:
|
||||
reverse_direction = D_DOWN;
|
||||
break;
|
||||
case D_DOWN:
|
||||
reverse_direction = D_UP;
|
||||
break;
|
||||
}
|
||||
return (con_orientation(con->parent) != orientation ||
|
||||
con->parent->layout == L_STACKED || con->parent->layout == L_TABBED ||
|
||||
con_descend_direction(con->parent, reverse_direction) == con);
|
||||
}
|
||||
|
||||
/*
|
||||
* The callback that is executed on every mouse move while dragging. On each
|
||||
* invocation we determine the drop target and the direction in which to insert
|
||||
* the dragged container. The indicator window is updated to show the new
|
||||
* position of the dragged container. The target container and direction are
|
||||
* passed out using the callback params.
|
||||
*
|
||||
*/
|
||||
DRAGGING_CB(drag_callback) {
|
||||
/* 30% of the container (minus the parent indicator) is used to drop the
|
||||
* dragged container as a sibling to the target */
|
||||
const double sibling_indicator_percent_of_rect = 0.3;
|
||||
/* Use the base decoration height and add a few pixels. This makes the
|
||||
* outer indicator generally thin but at least thick enough to cover
|
||||
* container titles */
|
||||
const double parent_indicator_max_size = render_deco_height() + logical_px(5);
|
||||
|
||||
Con *target = find_drop_target(new_x, new_y);
|
||||
if (target == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect rect = con_rect_plus_deco_height(target);
|
||||
|
||||
direction_t direction = 0;
|
||||
drop_type_t drop_type = DT_CENTER;
|
||||
bool draw_window = true;
|
||||
const struct callback_params *params = extra;
|
||||
|
||||
if (target->type == CT_WORKSPACE) {
|
||||
goto create_indicator;
|
||||
}
|
||||
|
||||
/* Define the thresholds in pixels. The drop type depends on the cursor
|
||||
* position. */
|
||||
const uint32_t min_rect_dimension = min(rect.width, rect.height);
|
||||
const uint32_t sibling_indicator_size = max(logical_px(2), (uint32_t)(sibling_indicator_percent_of_rect * min_rect_dimension));
|
||||
const uint32_t parent_indicator_size = min(
|
||||
parent_indicator_max_size,
|
||||
/* For small containers, start where the sibling indicator finishes.
|
||||
* This is always at least 1 pixel. We use min() to not override the
|
||||
* sibling indicator: */
|
||||
sibling_indicator_size - 1);
|
||||
|
||||
/* Find which edge the cursor is closer to. */
|
||||
const uint32_t d_left = new_x - rect.x;
|
||||
const uint32_t d_top = new_y - rect.y;
|
||||
const uint32_t d_right = rect.x + rect.width - new_x;
|
||||
const uint32_t d_bottom = rect.y + rect.height - new_y;
|
||||
const uint32_t d_min = min(min(d_left, d_right), min(d_top, d_bottom));
|
||||
/* And move the container towards that direction. */
|
||||
if (d_left == d_min) {
|
||||
direction = D_LEFT;
|
||||
} else if (d_top == d_min) {
|
||||
direction = D_UP;
|
||||
} else if (d_right == d_min) {
|
||||
direction = D_RIGHT;
|
||||
} else if (d_bottom == d_min) {
|
||||
direction = D_DOWN;
|
||||
} else {
|
||||
/* Keep the compiler happy */
|
||||
ELOG("min() is broken\n");
|
||||
assert(false);
|
||||
}
|
||||
const bool target_parent = (d_min < parent_indicator_size &&
|
||||
con_on_side_of_parent(target, direction));
|
||||
const bool target_sibling = (d_min < sibling_indicator_size);
|
||||
drop_type = target_parent ? DT_PARENT : (target_sibling ? DT_SIBLING : DT_CENTER);
|
||||
|
||||
/* target == con makes sense only when we are moving away from target's parent. */
|
||||
if (drop_type != DT_PARENT && target == con) {
|
||||
draw_window = false;
|
||||
xcb_destroy_window(conn, *(params->indicator));
|
||||
*(params->indicator) = 0;
|
||||
goto create_indicator;
|
||||
}
|
||||
|
||||
switch (drop_type) {
|
||||
case DT_PARENT:
|
||||
while (target->parent->type != CT_WORKSPACE && con_on_side_of_parent(target->parent, direction)) {
|
||||
target = target->parent;
|
||||
}
|
||||
rect = adjust_rect(target->parent->rect, direction, parent_indicator_size);
|
||||
break;
|
||||
case DT_CENTER:
|
||||
rect = target->rect;
|
||||
rect.x += sibling_indicator_size;
|
||||
rect.y += sibling_indicator_size;
|
||||
rect.width -= sibling_indicator_size * 2;
|
||||
rect.height -= sibling_indicator_size * 2;
|
||||
break;
|
||||
case DT_SIBLING:
|
||||
rect = adjust_rect(target->rect, direction, sibling_indicator_size);
|
||||
break;
|
||||
}
|
||||
|
||||
create_indicator:
|
||||
if (draw_window) {
|
||||
if (*(params->indicator) == 0) {
|
||||
*(params->indicator) = create_drop_indicator(rect);
|
||||
} else {
|
||||
const uint32_t values[4] = {rect.x, rect.y, rect.width, rect.height};
|
||||
const uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT;
|
||||
xcb_configure_window(conn, *(params->indicator), mask, values);
|
||||
}
|
||||
}
|
||||
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
|
||||
xcb_flush(conn);
|
||||
|
||||
*(params->target) = target;
|
||||
*(params->direction) = direction;
|
||||
*(params->drop_type) = drop_type;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a new drop indicator window with the given initial coordinates.
|
||||
*
|
||||
*/
|
||||
static xcb_window_t create_drop_indicator(Rect rect) {
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[2];
|
||||
|
||||
mask = XCB_CW_BACK_PIXEL;
|
||||
values[0] = config.client.focused.indicator.colorpixel;
|
||||
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[1] = 1;
|
||||
|
||||
xcb_window_t indicator = create_window(conn, rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_MOVE, false, mask, values);
|
||||
/* Change the window class to "i3-drag", so that it can be matched in a
|
||||
* compositor configuration. Note that the class needs to be changed before
|
||||
* mapping the window. */
|
||||
xcb_change_property(conn,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
indicator,
|
||||
XCB_ATOM_WM_CLASS,
|
||||
XCB_ATOM_STRING,
|
||||
8,
|
||||
(strlen("i3-drag") + 1) * 2,
|
||||
"i3-drag\0i3-drag\0");
|
||||
xcb_map_window(conn, indicator);
|
||||
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, indicator);
|
||||
|
||||
return indicator;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiates a mouse drag operation on a tiled window.
|
||||
*
|
||||
*/
|
||||
void tiling_drag(Con *con, xcb_button_press_event_t *event) {
|
||||
DLOG("Start dragging tiled container: con = %p\n", con);
|
||||
bool set_focus = (con == focused);
|
||||
bool set_fs = con->fullscreen_mode != CF_NONE;
|
||||
|
||||
/* Don't change focus while dragging. */
|
||||
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
|
||||
xcb_flush(conn);
|
||||
|
||||
/* Indicate drop location while dragging. This blocks until the drag is completed. */
|
||||
Con *target = NULL;
|
||||
direction_t direction;
|
||||
drop_type_t drop_type;
|
||||
xcb_window_t indicator = 0;
|
||||
const struct callback_params params = {&indicator, &target, &direction, &drop_type};
|
||||
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP, XCURSOR_CURSOR_MOVE, drag_callback, ¶ms);
|
||||
|
||||
/* Dragging is done. We don't need the indicator window any more. */
|
||||
xcb_destroy_window(conn, indicator);
|
||||
|
||||
if (drag_result == DRAG_REVERT ||
|
||||
target == NULL ||
|
||||
(target == con && drop_type != DT_PARENT) ||
|
||||
!con_exists(target)) {
|
||||
DLOG("drop aborted\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const orientation_t orientation = orientation_from_direction(direction);
|
||||
const position_t position = position_from_direction(direction);
|
||||
const layout_t layout = orientation == VERT ? L_SPLITV : L_SPLITH;
|
||||
con_disable_fullscreen(con);
|
||||
switch (drop_type) {
|
||||
case DT_CENTER:
|
||||
/* Also handles workspaces.*/
|
||||
DLOG("drop to center of %p\n", target);
|
||||
con_move_to_target(con, target);
|
||||
break;
|
||||
case DT_SIBLING:
|
||||
DLOG("drop %s %p\n", position_to_string(position), target);
|
||||
if (con_orientation(target->parent) != orientation) {
|
||||
/* If con and target are the only children of the same parent, we can just change
|
||||
* the parent's layout manually and then move con to the correct position.
|
||||
* tree_split checks for a parent with only one child so it would create a new
|
||||
* parent with the new layout. */
|
||||
if (con->parent == target->parent && con_num_children(target->parent) == 2) {
|
||||
target->parent->layout = layout;
|
||||
} else {
|
||||
tree_split(target, orientation);
|
||||
}
|
||||
}
|
||||
|
||||
insert_con_into(con, target, position);
|
||||
|
||||
ipc_send_window_event("move", con);
|
||||
break;
|
||||
case DT_PARENT: {
|
||||
const bool parent_tabbed_or_stacked = (target->parent->layout == L_TABBED || target->parent->layout == L_STACKED);
|
||||
DLOG("drop %s (%s) of %s%p\n",
|
||||
direction_to_string(direction),
|
||||
position_to_string(position),
|
||||
parent_tabbed_or_stacked ? "tabbed/stacked " : "",
|
||||
target);
|
||||
if (parent_tabbed_or_stacked) {
|
||||
/* When dealing with tabbed/stacked the target can be in the
|
||||
* middle of the container. Thus, after a directional move, con
|
||||
* will still be bound to the tabbed/stacked parent. */
|
||||
if (position == BEFORE) {
|
||||
target = TAILQ_FIRST(&(target->parent->nodes_head));
|
||||
} else {
|
||||
target = TAILQ_LAST(&(target->parent->nodes_head), nodes_head);
|
||||
}
|
||||
}
|
||||
if (con != target) {
|
||||
insert_con_into(con, target, position);
|
||||
}
|
||||
/* tree_move can change the focus */
|
||||
Con *old_focus = focused;
|
||||
tree_move(con, direction);
|
||||
if (focused != old_focus) {
|
||||
con_activate(old_focus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Warning: target might not exist anymore */
|
||||
target = NULL;
|
||||
|
||||
/* Manage fullscreen status. */
|
||||
if (set_focus || set_fs) {
|
||||
Con *fs = con_get_fullscreen_covering_ws(con_get_workspace(con));
|
||||
if (fs == con) {
|
||||
ELOG("dragged container somehow got fullscreen again.\n");
|
||||
assert(false);
|
||||
} else if (fs && set_focus && set_fs) {
|
||||
/* con was focused & fullscreen, disable other fullscreen container. */
|
||||
con_disable_fullscreen(fs);
|
||||
} else if (fs) {
|
||||
/* con was not focused, prefer other fullscreen container. */
|
||||
set_fs = set_focus = false;
|
||||
} else if (!set_focus) {
|
||||
/* con was not focused. If it was fullscreen and we are moving it to the focused
|
||||
* workspace we must focus it. */
|
||||
set_focus = (set_fs && con_get_workspace(focused) == con_get_workspace(con));
|
||||
}
|
||||
}
|
||||
if (set_fs) {
|
||||
con_enable_fullscreen(con, CF_OUTPUT);
|
||||
}
|
||||
if (set_focus) {
|
||||
workspace_show(con_get_workspace(con));
|
||||
con_focus(con);
|
||||
}
|
||||
tree_render();
|
||||
}
|
32
src/util.c
32
src/util.c
@ -476,3 +476,35 @@ direction_t direction_from_orientation_position(orientation_t orientation, posit
|
||||
return position == BEFORE ? D_UP : D_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts direction to a string representation.
|
||||
*
|
||||
*/
|
||||
const char *direction_to_string(direction_t direction) {
|
||||
switch (direction) {
|
||||
case D_LEFT:
|
||||
return "left";
|
||||
case D_RIGHT:
|
||||
return "right";
|
||||
case D_UP:
|
||||
return "up";
|
||||
case D_DOWN:
|
||||
return "down";
|
||||
}
|
||||
return "invalid";
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts position to a string representation.
|
||||
*
|
||||
*/
|
||||
const char *position_to_string(position_t position) {
|
||||
switch (position) {
|
||||
case BEFORE:
|
||||
return "before";
|
||||
case AFTER:
|
||||
return "after";
|
||||
}
|
||||
return "invalid";
|
||||
}
|
||||
|
13
src/window.c
13
src/window.c
@ -415,12 +415,10 @@ 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
|
||||
bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
|
||||
/* 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
|
||||
@ -435,7 +433,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
|
||||
|
||||
if (prop == NULL || 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
|
||||
@ -461,6 +459,7 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
|
||||
}
|
||||
|
||||
FREE(prop);
|
||||
return true;
|
||||
|
||||
#undef MWM_HINTS_FLAGS_FIELD
|
||||
#undef MWM_HINTS_DECORATIONS_FIELD
|
||||
|
@ -299,16 +299,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
||||
|
||||
/*
|
||||
* Returns true if the workspace is currently visible. Especially important for
|
||||
* multi-monitor environments, as they can have multiple currenlty active
|
||||
* multi-monitor environments, as they can have multiple currently active
|
||||
* workspaces.
|
||||
*
|
||||
*/
|
||||
bool workspace_is_visible(Con *ws) {
|
||||
Con *output = con_get_output(ws);
|
||||
if (output == NULL)
|
||||
if (output == NULL) {
|
||||
return false;
|
||||
}
|
||||
Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
|
||||
LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
|
||||
return (fs == ws);
|
||||
}
|
||||
|
||||
|
38
src/x.c
38
src/x.c
@ -490,14 +490,20 @@ void x_draw_decoration(Con *con) {
|
||||
struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params));
|
||||
|
||||
/* find out which colors to use */
|
||||
if (con->urgent)
|
||||
if (con->urgent) {
|
||||
p->color = &config.client.urgent;
|
||||
else if (con == focused || con_inside_focused(con))
|
||||
} else if (con == focused || con_inside_focused(con)) {
|
||||
p->color = &config.client.focused;
|
||||
else if (con == TAILQ_FIRST(&(parent->focus_head)))
|
||||
p->color = &config.client.focused_inactive;
|
||||
else
|
||||
} else if (con == TAILQ_FIRST(&(parent->focus_head))) {
|
||||
if (config.client.got_focused_tab_title && !leaf && con_descend_focused(con) == focused) {
|
||||
/* Stacked/tabbed parent of focused container */
|
||||
p->color = &config.client.focused_tab_title;
|
||||
} else {
|
||||
p->color = &config.client.focused_inactive;
|
||||
}
|
||||
} else {
|
||||
p->color = &config.client.unfocused;
|
||||
}
|
||||
|
||||
p->border_style = con_border_style(con);
|
||||
|
||||
@ -875,7 +881,6 @@ void x_push_node(Con *con) {
|
||||
con_state *state;
|
||||
Rect rect = con->rect;
|
||||
|
||||
//DLOG("Pushing changes for node %p / %s\n", con, con->name);
|
||||
state = state_for_frame(con->frame.id);
|
||||
|
||||
if (state->name != NULL) {
|
||||
@ -988,14 +993,16 @@ void x_push_node(Con *con) {
|
||||
win_depth = con->window->depth;
|
||||
|
||||
/* Ensure we have valid dimensions for our surface. */
|
||||
// TODO This is probably a bug in the condition above as we should never enter this path
|
||||
// for height == 0. Also, we should probably handle width == 0 the same way.
|
||||
/* TODO: This is probably a bug in the condition above as we should
|
||||
* never enter this path for height == 0. Also, we should probably
|
||||
* handle width == 0 the same way. */
|
||||
int width = MAX((int32_t)rect.width, 1);
|
||||
int height = MAX((int32_t)rect.height, 1);
|
||||
|
||||
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);
|
||||
draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
|
||||
|
||||
/* For the graphics context, we disable GraphicsExposure events.
|
||||
* Those will be sent when a CopyArea request cannot be fulfilled
|
||||
@ -1008,8 +1015,8 @@ void x_push_node(Con *con) {
|
||||
con->pixmap_recreated = true;
|
||||
|
||||
/* Don’t render the decoration for windows inside a stack which are
|
||||
* not visible right now */
|
||||
// TODO Should this work the same way for L_TABBED?
|
||||
* not visible right now
|
||||
* TODO: Should this work the same way for L_TABBED? */
|
||||
if (!con->parent ||
|
||||
con->parent->layout != L_STACKED ||
|
||||
TAILQ_FIRST(&(con->parent->focus_head)) == con)
|
||||
@ -1120,7 +1127,6 @@ static void x_push_node_unmaps(Con *con) {
|
||||
Con *current;
|
||||
con_state *state;
|
||||
|
||||
//DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name);
|
||||
state = state_for_frame(con->frame.id);
|
||||
|
||||
/* map/unmap if map state changed, also ensure that the child window
|
||||
@ -1196,7 +1202,6 @@ void x_push_changes(Con *con) {
|
||||
}
|
||||
|
||||
DLOG("-- PUSHING WINDOW STACK --\n");
|
||||
//DLOG("Disabling EnterNotify\n");
|
||||
/* We need to keep SubstructureRedirect around, otherwise clients can send
|
||||
* ConfigureWindow requests and get them applied directly instead of having
|
||||
* them become ConfigureRequests that i3 handles. */
|
||||
@ -1205,7 +1210,6 @@ void x_push_changes(Con *con) {
|
||||
if (state->mapped)
|
||||
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
|
||||
}
|
||||
//DLOG("Done, EnterNotify disabled\n");
|
||||
bool order_changed = false;
|
||||
bool stacking_changed = false;
|
||||
|
||||
@ -1235,14 +1239,12 @@ void x_push_changes(Con *con) {
|
||||
if (con_has_managed_window(state->con))
|
||||
memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t));
|
||||
|
||||
//DLOG("stack: 0x%08x\n", state->id);
|
||||
con_state *prev = CIRCLEQ_PREV(state, state);
|
||||
con_state *old_prev = CIRCLEQ_PREV(state, old_state);
|
||||
if (prev != old_prev)
|
||||
order_changed = true;
|
||||
if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
|
||||
stacking_changed = true;
|
||||
//DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
|
||||
uint32_t mask = 0;
|
||||
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
||||
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
|
||||
@ -1295,13 +1297,11 @@ void x_push_changes(Con *con) {
|
||||
warp_to = NULL;
|
||||
}
|
||||
|
||||
//DLOG("Re-enabling EnterNotify\n");
|
||||
values[0] = FRAME_EVENT_MASK;
|
||||
CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) {
|
||||
if (state->mapped)
|
||||
xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
|
||||
}
|
||||
//DLOG("Done, EnterNotify re-enabled\n");
|
||||
|
||||
x_deco_recurse(con);
|
||||
|
||||
@ -1387,9 +1387,6 @@ void x_push_changes(Con *con) {
|
||||
CIRCLEQ_REMOVE(&old_state_head, state, old_state);
|
||||
CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state);
|
||||
}
|
||||
//CIRCLEQ_FOREACH(state, &old_state_head, old_state) {
|
||||
// DLOG("old stack: 0x%08x\n", state->id);
|
||||
//}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
@ -1402,7 +1399,6 @@ void x_push_changes(Con *con) {
|
||||
void x_raise_con(Con *con) {
|
||||
con_state *state;
|
||||
state = state_for_frame(con->frame.id);
|
||||
//DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
|
||||
|
||||
CIRCLEQ_REMOVE(&state_head, state, state);
|
||||
CIRCLEQ_INSERT_HEAD(&state_head, state, state);
|
||||
|
@ -433,7 +433,7 @@ int main(int argc, char *argv[]) {
|
||||
bound = true;
|
||||
/* Let the user know bind() was successful, so that they know the
|
||||
* error messages can be disregarded. */
|
||||
fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path);
|
||||
fprintf(stderr, "Successfully bound to %s\n", addr.sun_path);
|
||||
sun_path = sstrdup(addr.sun_path);
|
||||
break;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ our @EXPORT = qw(
|
||||
exit_forcefully
|
||||
workspace_exists
|
||||
focused_ws
|
||||
focused_output
|
||||
get_socket_path
|
||||
launch_with_config
|
||||
get_i3_log
|
||||
@ -664,6 +665,25 @@ sub workspace_exists {
|
||||
(scalar grep { $_ eq $name } @{get_workspace_names()}) > 0;
|
||||
}
|
||||
|
||||
=head2 focused_output
|
||||
|
||||
Returns the name of the currently focused output.
|
||||
|
||||
is(focused_output, 'fake-0', 'i3 starts on output 0');
|
||||
|
||||
=cut
|
||||
sub _focused_output {
|
||||
my $i3 = i3(get_socket_path());
|
||||
my $tree = $i3->get_tree->recv;
|
||||
my $focused = $tree->{focus}->[0];
|
||||
my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
|
||||
return $output;
|
||||
}
|
||||
|
||||
sub focused_output {
|
||||
return _focused_output->{name}
|
||||
}
|
||||
|
||||
=head2 focused_ws
|
||||
|
||||
Returns the name of the currently focused workspace.
|
||||
@ -672,11 +692,9 @@ Returns the name of the currently focused workspace.
|
||||
is($ws, '1', 'i3 starts on workspace 1');
|
||||
|
||||
=cut
|
||||
|
||||
sub focused_ws {
|
||||
my $i3 = i3(get_socket_path());
|
||||
my $tree = $i3->get_tree->recv;
|
||||
my $focused = $tree->{focus}->[0];
|
||||
my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
|
||||
my $output = _focused_output;
|
||||
my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
|
||||
my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
|
||||
return $first->{name}
|
||||
@ -780,6 +798,8 @@ sub exit_gracefully {
|
||||
my ($pid, $socketpath) = @_;
|
||||
$socketpath ||= get_socket_path();
|
||||
|
||||
$SIG{CHLD} = undef;
|
||||
|
||||
my $exited = 0;
|
||||
eval {
|
||||
say "Exiting i3 cleanly...";
|
||||
@ -818,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)
|
||||
@ -941,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) });
|
||||
@ -972,6 +1006,8 @@ sub kill_all_windows {
|
||||
# Sync in case not all windows are managed by i3 just yet.
|
||||
sync_with_i3;
|
||||
cmd '[title=".*"] kill';
|
||||
# Sync to make sure x_window_kill() calls have taken effect.
|
||||
sync_with_i3;
|
||||
}
|
||||
|
||||
=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
|
||||
|
@ -14,7 +14,7 @@
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Tests whether we can switch to a non-existant workspace
|
||||
# Tests whether we can switch to a non-existent workspace
|
||||
# (necessary for further tests)
|
||||
#
|
||||
use List::Util qw(first);
|
||||
|
@ -267,7 +267,7 @@ like($output->[2], qr|^bindsym Mod1\+f resize shrink right 20 px$|, 'resize righ
|
||||
like($output->[3], qr|^bindsym Mod1\+f resize grow down 23 px$|, 'resize bottom changed');
|
||||
|
||||
#####################################################################
|
||||
# also resizing, but with indention this time
|
||||
# also resizing, but with indentation this time
|
||||
#####################################################################
|
||||
|
||||
like($output->[4], qr|^bindsym Mod1\+f resize grow left 10 px$|, 'resize left changed');
|
||||
|
@ -476,16 +476,18 @@ is(parser_calls($config),
|
||||
################################################################################
|
||||
|
||||
$config = <<'EOT';
|
||||
client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
|
||||
client.focused_inactive #333333 #5f676a #ffffff #484e50
|
||||
client.unfocused #333333 #222222 #888888 #292d2e
|
||||
client.urgent #2f343a #900000 #ffffff #900000 #c00000
|
||||
client.placeholder #000000 #0c0c0c #ffffff #000000
|
||||
client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
|
||||
client.focused_inactive #333333 #5f676a #ffffff #484e50
|
||||
client.focused_tab_title #444444 #555555 #ffffff
|
||||
client.unfocused #333333 #222222 #888888 #292d2e
|
||||
client.urgent #2f343a #900000 #ffffff #900000 #c00000
|
||||
client.placeholder #000000 #0c0c0c #ffffff #000000
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, #b34d4c)
|
||||
cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50, NULL)
|
||||
cfg_color(client.focused_tab_title, #444444, #555555, #ffffff, NULL, NULL)
|
||||
cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e, NULL)
|
||||
cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000, #c00000)
|
||||
cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000, NULL)
|
||||
@ -551,6 +553,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
|
||||
exec
|
||||
client.background
|
||||
client.focused_inactive
|
||||
client.focused_tab_title
|
||||
client.focused
|
||||
client.unfocused
|
||||
client.urgent
|
||||
|
@ -71,7 +71,7 @@ cmd 'focus left';
|
||||
cmd 'mark left';
|
||||
|
||||
#
|
||||
# get_marks replys an array of marks, whose order is undefined,
|
||||
# get_marks replies an array of marks, whose order is undefined,
|
||||
# so we use sort to be able to compare the output
|
||||
#
|
||||
|
||||
|
@ -18,7 +18,7 @@ use i3test i3_config => <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
# fake-1 under fake-0 to not interfere with left/right wraping
|
||||
# fake-1 under fake-0 to not interfere with left/right wrapping
|
||||
fake-outputs 1024x768+0+0,1024x768+0+1024
|
||||
workspace X output fake-1
|
||||
EOT
|
||||
@ -56,7 +56,11 @@ sub kill_subtest {
|
||||
my $focus = AnyEvent->condvar;
|
||||
|
||||
my @events = events_for(
|
||||
sub { cmd $cmd },
|
||||
sub {
|
||||
cmd $cmd;
|
||||
# Sync to make sure x_window_kill() calls have taken effect.
|
||||
sync_with_i3;
|
||||
},
|
||||
'window');
|
||||
|
||||
is(scalar @events, 1, 'Received 1 event');
|
||||
|
@ -62,7 +62,7 @@ $config = <<'EOT';
|
||||
font \
|
||||
-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
# Use line contiuation with too many lines (>4096 characters).
|
||||
# Use line continuation with too many lines (>4096 characters).
|
||||
# This config is invalid. Use it to ensure no buffer overflow.
|
||||
bindsym Mod1+b \
|
||||
0001-This is a very very very very very very very very very very very very very very very very very long cmd \
|
||||
|
@ -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;
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Verify that the corrent focus stack order is preserved after various
|
||||
# Verify that the current focus stack order is preserved after various
|
||||
# operations.
|
||||
use i3test i3_config => <<EOT;
|
||||
# i3 config file (v4)
|
||||
|
@ -43,9 +43,9 @@ void init_ctx(void *connptr) {
|
||||
* | C |
|
||||
* +-------+
|
||||
*
|
||||
* - Zone A is completly opaque.
|
||||
* - Zone A is completely opaque.
|
||||
* - Zone B is clickable through (input shape).
|
||||
* - Zone C is completly transparent (bounding shape).
|
||||
* - Zone C is completely transparent (bounding shape).
|
||||
*/
|
||||
void set_shape(long window_id) {
|
||||
xcb_rectangle_t bounding_rectangle = { 0, 0, 100, 50 };
|
||||
|
@ -22,7 +22,7 @@ use i3test i3_config => <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
# fake-1 under fake-0 to not interfere with left/right wraping
|
||||
# fake-1 under fake-0 to not interfere with left/right wrapping
|
||||
fake-outputs 1024x768+0+0,1024x768+0+1024
|
||||
workspace X output fake-1
|
||||
EOT
|
||||
|
30
testcases/t/314-window-icon-padding.t
Normal file → Executable file
30
testcases/t/314-window-icon-padding.t
Normal file → Executable file
@ -38,6 +38,36 @@ is(window_icon_padding($tmp), -1, 'window_icon_padding defaults to -1');
|
||||
cmd 'title_window_icon on';
|
||||
isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1');
|
||||
|
||||
cmd 'title_window_icon toggle';
|
||||
is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
|
||||
|
||||
cmd 'title_window_icon toggle';
|
||||
isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1 again');
|
||||
|
||||
cmd 'title_window_icon off';
|
||||
is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
|
||||
|
||||
cmd 'title_window_icon padding 3px';
|
||||
is(window_icon_padding($tmp), 3, 'window_icon_padding set to 3');
|
||||
|
||||
cmd 'title_window_icon toggle';
|
||||
ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
|
||||
|
||||
cmd 'title_window_icon toggle';
|
||||
is(window_icon_padding($tmp), 3, 'window_icon_padding toggled back to 3');
|
||||
|
||||
cmd 'title_window_icon toggle 5px';
|
||||
ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
|
||||
|
||||
cmd 'title_window_icon toggle 5px';
|
||||
is(window_icon_padding($tmp), 5, 'window_icon_padding toggled on to 5px');
|
||||
|
||||
cmd 'title_window_icon toggle 5px';
|
||||
ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
|
||||
|
||||
cmd 'title_window_icon toggle 4px';
|
||||
is(window_icon_padding($tmp), 4, 'window_icon_padding toggled on to 4px');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
################################################################################
|
||||
|
46
testcases/t/315-long-commands.t
Normal file
46
testcases/t/315-long-commands.t
Normal file
@ -0,0 +1,46 @@
|
||||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • https://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Test that commands with more than 10 non-identified words doesn't works
|
||||
# 10 is the magic number chosen for the stack size which is why it's used here
|
||||
# Ticket: #2968
|
||||
# Bug still in: 4.19.2-103-gfc65ca36
|
||||
use i3test;
|
||||
|
||||
######################################################################
|
||||
# 1) run a long command
|
||||
######################################################################
|
||||
|
||||
my $i3 = i3(get_socket_path());
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
my $floatwin = open_floating_window;
|
||||
|
||||
|
||||
my ($absolute_before, $top_before) = $floatwin->rect;
|
||||
|
||||
cmd 'move window container to window container to window container to left';
|
||||
|
||||
sync_with_i3;
|
||||
|
||||
my ($absolute, $top) = $floatwin->rect;
|
||||
|
||||
is($absolute->x, ($absolute_before->x - 10), 'moved 10 px to the left');
|
||||
is($absolute->y, $absolute_before->y, 'y not changed');
|
||||
is($absolute->width, $absolute_before->width, 'width not changed');
|
||||
is($absolute->height, $absolute_before->height, 'height not changed');
|
||||
|
||||
done_testing;
|
321
testcases/t/316-drag-container.t
Normal file
321
testcases/t/316-drag-container.t
Normal file
@ -0,0 +1,321 @@
|
||||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • https://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Test dragging containers.
|
||||
|
||||
my ($width, $height) = (1000, 500);
|
||||
|
||||
my $config = <<"EOT";
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
focus_follows_mouse no
|
||||
floating_modifier Mod1
|
||||
|
||||
# 2 side by side outputs
|
||||
fake-outputs ${width}x${height}+0+0P,${width}x${height}+${width}+0
|
||||
|
||||
bar {
|
||||
output primary
|
||||
}
|
||||
EOT
|
||||
use i3test i3_autostart => 0;
|
||||
use i3test::XTEST;
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
sub start_drag {
|
||||
my ($pos_x, $pos_y) = @_;
|
||||
die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
|
||||
|
||||
$x->root->warp_pointer($pos_x, $pos_y);
|
||||
sync_with_i3;
|
||||
|
||||
xtest_key_press(64); # Alt_L
|
||||
xtest_button_press(1, $pos_x, $pos_y);
|
||||
xtest_sync_with_i3;
|
||||
}
|
||||
|
||||
sub end_drag {
|
||||
my ($pos_x, $pos_y) = @_;
|
||||
die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
|
||||
|
||||
$x->root->warp_pointer($pos_x, $pos_y);
|
||||
sync_with_i3;
|
||||
|
||||
xtest_button_release(1, $pos_x, $pos_y);
|
||||
xtest_key_release(64); # Alt_L
|
||||
xtest_sync_with_i3;
|
||||
}
|
||||
|
||||
my ($ws1, $ws2);
|
||||
my ($A, $B, $tmp);
|
||||
my ($A_id, $B_id);
|
||||
|
||||
sub move_subtest {
|
||||
my ($cb, $win) = @_;
|
||||
|
||||
my @events = events_for($cb, 'window');
|
||||
my @move = grep { $_->{change} eq 'move' } @events;
|
||||
|
||||
is(scalar @move, 1, 'Received 1 window::move event');
|
||||
is($move[0]->{container}->{window}, $A->{id}, "window id matches");
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Drag floating container onto an empty workspace.
|
||||
###############################################################################
|
||||
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
$A = open_floating_window(rect => [ 30, 30, 50, 50 ]);
|
||||
|
||||
start_drag(40, 40);
|
||||
end_drag(1050, 50);
|
||||
|
||||
is($x->input_focus, $A->id, 'Floating window moved to the right workspace');
|
||||
is($ws2, focused_ws, 'Empty workspace focused after floating window dragged to it');
|
||||
|
||||
###############################################################################
|
||||
# Drag tiling container onto an empty workspace.
|
||||
###############################################################################
|
||||
|
||||
subtest "Draging tiling container onto an empty workspace produces move event", \&move_subtest,
|
||||
sub {
|
||||
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
$A = open_window;
|
||||
|
||||
start_drag(50, 50);
|
||||
end_drag(1050, 50);
|
||||
|
||||
is($x->input_focus, $A->id, 'Tiling window moved to the right workspace');
|
||||
is($ws2, focused_ws, 'Empty workspace focused after tiling window dragged to it');
|
||||
|
||||
};
|
||||
|
||||
###############################################################################
|
||||
# Drag tiling container onto a container that closes before the drag is
|
||||
# complete.
|
||||
###############################################################################
|
||||
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
$A = open_window;
|
||||
open_window;
|
||||
|
||||
start_drag(600, 300); # Start dragging the second window.
|
||||
|
||||
# Try to place it on the first window.
|
||||
$x->root->warp_pointer(50, 50);
|
||||
sync_with_i3;
|
||||
|
||||
cmd '[id=' . $A->id . '] kill';
|
||||
sync_with_i3;
|
||||
end_drag(50, 50);
|
||||
|
||||
is(@{get_ws_content($ws1)}, 1, 'One container left in ws1');
|
||||
|
||||
###############################################################################
|
||||
# Drag tiling container onto a tiling container on an other workspace.
|
||||
###############################################################################
|
||||
|
||||
subtest "Draging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest,
|
||||
sub {
|
||||
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
open_window;
|
||||
$B_id = get_focused($ws2);
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
$A = open_window;
|
||||
$A_id = get_focused($ws1);
|
||||
|
||||
start_drag(50, 50);
|
||||
end_drag(1500, 250); # Center of right output, inner region.
|
||||
|
||||
is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it');
|
||||
$ws2 = get_ws($ws2);
|
||||
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
|
||||
is($ws2->{focus}[1], $B_id, 'B focused second');
|
||||
|
||||
};
|
||||
|
||||
###############################################################################
|
||||
# Drag tiling container onto a floating container on an other workspace.
|
||||
###############################################################################
|
||||
|
||||
subtest "Draging tiling container onto a floating container on an other workspace produces move event", \&move_subtest,
|
||||
sub {
|
||||
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
open_floating_window;
|
||||
$B_id = get_focused($ws2);
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
$A = open_window;
|
||||
$A_id = get_focused($ws1);
|
||||
|
||||
start_drag(50, 50);
|
||||
end_drag(1500, 250);
|
||||
|
||||
is($ws2, focused_ws, 'Workspace with one floating container focused after tiling window dragged to it');
|
||||
$ws2 = get_ws($ws2);
|
||||
is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
|
||||
is($ws2->{floating_nodes}[0]->{nodes}[0]->{id}, $B_id, 'B exists & floating');
|
||||
|
||||
};
|
||||
|
||||
###############################################################################
|
||||
# Drag tiling container onto a bar.
|
||||
###############################################################################
|
||||
|
||||
subtest "Draging tiling container onto a bar produces move event", \&move_subtest,
|
||||
sub {
|
||||
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
open_window;
|
||||
$B_id = get_focused($ws1);
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
$A = open_window;
|
||||
$A_id = get_focused($ws2);
|
||||
|
||||
start_drag(1500, 250);
|
||||
end_drag(1, 498); # Bar on bottom of left output.
|
||||
|
||||
is($ws1, focused_ws, 'Workspace focused after tiling window dragged to its bar');
|
||||
$ws1 = get_ws($ws1);
|
||||
is($ws1->{focus}[0], $A_id, 'B focused first, dragged container kept focus');
|
||||
is($ws1->{focus}[1], $B_id, 'A focused second');
|
||||
|
||||
};
|
||||
|
||||
###############################################################################
|
||||
# Drag an unfocused tiling container onto it's self.
|
||||
###############################################################################
|
||||
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
open_window;
|
||||
$A_id = get_focused($ws1);
|
||||
open_window;
|
||||
$B_id = get_focused($ws1);
|
||||
|
||||
start_drag(50, 50);
|
||||
end_drag(450, 450);
|
||||
|
||||
$ws1 = get_ws($ws1);
|
||||
is($ws1->{focus}[0], $B_id, 'B focused first, kept focus');
|
||||
is($ws1->{focus}[1], $A_id, 'A focused second, unfocused dragged container didn\'t gain focus');
|
||||
|
||||
###############################################################################
|
||||
# Drag an unfocused tiling container onto an occupied workspace.
|
||||
###############################################################################
|
||||
|
||||
subtest "Draging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest,
|
||||
sub {
|
||||
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
$A = open_window;
|
||||
$A_id = get_focused($ws1);
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
open_window;
|
||||
$B_id = get_focused($ws2);
|
||||
|
||||
start_drag(50, 50);
|
||||
end_drag(1500, 250); # Center of right output, inner region.
|
||||
|
||||
is($ws2, focused_ws, 'Workspace remained focused after dragging unfocused container');
|
||||
$ws2 = get_ws($ws2);
|
||||
is($ws2->{focus}[0], $B_id, 'B focused first, kept focus');
|
||||
is($ws2->{focus}[1], $A_id, 'A focused second, unfocused container didn\'t steal focus');
|
||||
|
||||
};
|
||||
|
||||
###############################################################################
|
||||
# Drag fullscreen container onto window in same workspace.
|
||||
###############################################################################
|
||||
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
open_window;
|
||||
$A = open_window;
|
||||
cmd 'fullscreen enable';
|
||||
|
||||
start_drag(900, 100); # Second window
|
||||
end_drag(50, 50); # To first window
|
||||
|
||||
is($ws1, focused_ws, 'Workspace remained focused after dragging fullscreen container');
|
||||
is_num_fullscreen($ws1, 1, 'Container still fullscreened');
|
||||
is($x->input_focus, $A->id, 'Fullscreen container still focused');
|
||||
|
||||
###############################################################################
|
||||
# Drag unfocused fullscreen container onto window in other workspace.
|
||||
###############################################################################
|
||||
|
||||
subtest "Draging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest,
|
||||
sub {
|
||||
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
$A = open_window;
|
||||
cmd 'fullscreen enable';
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
open_window;
|
||||
open_window;
|
||||
|
||||
start_drag(900, 100);
|
||||
end_drag(1000 + 500 * 0.15 + 10, 200); # left of leftmost window
|
||||
|
||||
is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
|
||||
is_num_fullscreen($ws1, 0, 'No fullscreen container in first workspace');
|
||||
is_num_fullscreen($ws2, 1, 'Moved container still fullscreened');
|
||||
is($x->input_focus, $A->id, 'Fullscreen container now focused');
|
||||
$ws2 = get_ws($ws2);
|
||||
is($ws2->{nodes}->[0]->{window}, $A->id, 'Fullscreen container now leftmost window in second workspace');
|
||||
|
||||
};
|
||||
|
||||
###############################################################################
|
||||
# Drag unfocused fullscreen container onto left outter region of window in
|
||||
# other workspace. The container shouldn't end up in $ws2 because it was
|
||||
# dragged onto the outter region of the leftmost window. We must also check
|
||||
# that the focus remains on the other window.
|
||||
###############################################################################
|
||||
|
||||
subtest "Draging unfocused fullscreen container onto left outter region of window in other workspace produces move event", \&move_subtest,
|
||||
sub {
|
||||
|
||||
$ws1 = fresh_workspace(output => 0);
|
||||
open_window for (1..3);
|
||||
$A = open_window;
|
||||
$tmp = get_focused($ws1);
|
||||
cmd 'fullscreen enable';
|
||||
$ws2 = fresh_workspace(output => 1);
|
||||
$B = open_window;
|
||||
|
||||
start_drag(990, 100); # rightmost of $ws1
|
||||
end_drag(1004, 100); # outter region of window of $ws2
|
||||
|
||||
is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
|
||||
is_num_fullscreen($ws1, 1, 'Fullscreen container still in first workspace');
|
||||
is_num_fullscreen($ws2, 0, 'No fullscreen container in second workspace');
|
||||
is($x->input_focus, $B->id, 'Window of second workspace still has focus');
|
||||
is(get_focused($ws1), $tmp, 'Fullscreen container still focused in first workspace');
|
||||
$ws1 = get_ws($ws1);
|
||||
is($ws1->{nodes}->[3]->{window}, $A->id, 'Fullscreen container still rightmost window in first workspace');
|
||||
|
||||
};
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
###############################################################################
|
||||
|
||||
done_testing;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user