diff options
author | Michael Stapelberg <michael@stapelberg.de> | 2022-09-21 18:26:55 +0200 |
---|---|---|
committer | Michael Stapelberg <michael@stapelberg.de> | 2022-09-21 18:26:55 +0200 |
commit | c0ef3caec86a0a80a10d641ba04daf217d875b95 (patch) | |
tree | a1aacac1afd69f55421c75a7d835176e0693810b | |
parent | d83940a8fc9d9a617257a6c9d0a9f74bbf3bb4ef (diff) | |
parent | 2bdcae8149c29e77d0b034f6fbc1be9ca180ab3f (diff) | |
download | i3-c0ef3caec86a0a80a10d641ba04daf217d875b95.tar.gz i3-c0ef3caec86a0a80a10d641ba04daf217d875b95.zip |
Merge branch 'next' into stable
119 files changed, 2107 insertions, 521 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a8aea200..f033a48d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 655382f5..0bba6a04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/RELEASE-NOTES-4.20.1 b/RELEASE-NOTES-4.20.1 deleted file mode 100644 index 62127460..00000000 --- a/RELEASE-NOTES-4.20.1 +++ /dev/null @@ -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 diff --git a/RELEASE-NOTES-4.21 b/RELEASE-NOTES-4.21 new file mode 100644 index 00000000..9d4c708e --- /dev/null +++ b/RELEASE-NOTES-4.21 @@ -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 diff --git a/debian/changelog b/debian/changelog index e39f5add..19d0ae53 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 diff --git a/debian/control b/debian/control index 5cd27cd6..6ddc64e6 100644 --- a/debian/control +++ b/debian/control @@ -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, diff --git a/debian/rules b/debian/rules index 26e303e6..e8323c24 100755 --- a/debian/rules +++ b/debian/rules @@ -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 @@ -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 diff --git a/docs/testsuite b/docs/testsuite index d681549c..ec87429c 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -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 diff --git a/docs/userguide b/docs/userguide index f3162264..7b233844 100644 --- a/docs/userguide +++ b/docs/userguide @@ -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*: diff --git a/generate-command-parser.pl b/generate-command-parser.pl index cef4eda5..17728736 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -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 '};'; } diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 25117f2b..c5ec071e 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -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; } diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop index 09670f65..e43d95aa 100755 --- a/i3-dmenu-desktop +++ b/i3-dmenu-desktop @@ -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'; diff --git a/i3-input/keysym.map b/i3-input/keysym.map index b198dd6d..aaecdfc3 100644 --- a/i3-input/keysym.map +++ b/i3-input/keysym.map @@ -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 diff --git a/i3-sensible-pager b/i3-sensible-pager index 386e2988..2d824684 100755 --- a/i3-sensible-pager +++ b/i3-sensible-pager @@ -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. diff --git a/i3-sensible-terminal b/i3-sensible-terminal index fa1443c4..bee303f8 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -14,7 +14,7 @@ # 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator # 3. The terminal emulator with best accessibility comes first. # 4. No order is guaranteed/desired for the remaining terminal emulators. -for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; 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 diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 52f77b0e..e0f2e7e4 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -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. */ diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 1cff085e..14d54ed6 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -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; } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 671588fe..1cfd457b 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -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; diff --git a/include/all.h b/include/all.h index b9a1a7a9..5941b5e6 100644 --- a/include/all.h +++ b/include/all.h @@ -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" diff --git a/include/commands.h b/include/commands.h index c27cba4a..d0f0140b 100644 --- a/include/commands.h +++ b/include/commands.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); diff --git a/include/commands_parser.h b/include/commands_parser.h index 31333af7..7e1c5203 100644 --- a/include/commands_parser.h +++ b/include/commands_parser.h @@ -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. */ diff --git a/include/con.h b/include/con.h index d8330098..b8bff080 100644 --- a/include/con.h +++ b/include/con.h @@ -209,6 +209,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. * diff --git a/include/config_parser.h b/include/config_parser.h index 00d01e45..82c57090 100644 --- a/include/config_parser.h +++ b/include/config_parser.h @@ -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. * diff --git a/include/configuration.h b/include/configuration.h index be072cf8..843e156f 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -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; diff --git a/include/data.h b/include/data.h index 95acd66d..1b3c7119 100644 --- a/include/data.h +++ b/include/data.h @@ -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 diff --git a/include/display_version.h b/include/display_version.h index d8dac30b..6996038e 100644 --- a/include/display_version.h +++ b/include/display_version.h @@ -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) diff --git a/include/i3.h b/include/i3.h index 15ca1d46..4e2eb5c0 100644 --- a/include/i3.h +++ b/include/i3.h @@ -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; diff --git a/include/libi3.h b/include/libi3.h index fd5fb03b..005167c7 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -295,6 +295,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. * diff --git a/include/randr.h b/include/randr.h index ae6a20a9..6fd7ea99 100644 --- a/include/randr.h +++ b/include/randr.h @@ -51,12 +51,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. * */ diff --git a/include/tiling_drag.h b/include/tiling_drag.h new file mode 100644 index 00000000..ab002d43 --- /dev/null +++ b/include/tiling_drag.h @@ -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); diff --git a/include/util.h b/include/util.h index 09ad941f..8525b6d9 100644 --- a/include/util.h +++ b/include/util.h @@ -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); diff --git a/include/window.h b/include/window.h index 9759cc4b..7b43ab1f 100644 --- a/include/window.h +++ b/include/window.h @@ -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 diff --git a/include/workspace.h b/include/workspace.h index 2193ed0b..fe6d9f88 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -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. * */ diff --git a/libi3/create_socket.c b/libi3/create_socket.c index 4b93ff2d..d476f43a 100644 --- a/libi3/create_socket.c +++ b/libi3/create_socket.c @@ -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); diff --git a/libi3/font.c b/libi3/font.c index 514328f6..5a11b536 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -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; diff --git a/libi3/ipc_connect.c b/libi3/ipc_connect.c index 871fe083..5da9f129 100644 --- a/libi3/ipc_connect.c +++ b/libi3/ipc_connect.c @@ -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; } diff --git a/man/i3-input.man b/man/i3-input.man index dc145914..9eb6c541 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -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. diff --git a/meson.build b/meson.build index 27fc9fb5..8c139c2a 100644 --- a/meson.build +++ b/meson.build @@ -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', diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 7d3367ea..486f20dd 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -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) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index a52a769f..617ed130 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -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: diff --git a/release-notes/bugfixes/0-example b/release-notes/bugfixes/0-example new file mode 100644 index 00000000..2cb1f668 --- /dev/null +++ b/release-notes/bugfixes/0-example @@ -0,0 +1 @@ +fix crash with "layout default" diff --git a/release-notes/bugfixes/1-replace-socket b/release-notes/bugfixes/1-replace-socket new file mode 100644 index 00000000..82c78b61 --- /dev/null +++ b/release-notes/bugfixes/1-replace-socket @@ -0,0 +1 @@ +Do not replace existing IPC socket on start diff --git a/release-notes/bugfixes/2-fix-focus-wrap b/release-notes/bugfixes/2-fix-focus-wrap new file mode 100644 index 00000000..3971238d --- /dev/null +++ b/release-notes/bugfixes/2-fix-focus-wrap @@ -0,0 +1 @@ +fix focus when moving container between outputs with mouse warp and focus_follows_mouse diff --git a/release-notes/bugfixes/3-transient_for b/release-notes/bugfixes/3-transient_for new file mode 100644 index 00000000..c652874f --- /dev/null +++ b/release-notes/bugfixes/3-transient_for @@ -0,0 +1 @@ +Fix endless loop with transient_for windows diff --git a/release-notes/bugfixes/4-failed-workspace-output b/release-notes/bugfixes/4-failed-workspace-output new file mode 100644 index 00000000..b3d23957 --- /dev/null +++ b/release-notes/bugfixes/4-failed-workspace-output @@ -0,0 +1 @@ +fix wrong failed reply on move workspace to output diff --git a/release-notes/bugfixes/5-fix-wm-registration b/release-notes/bugfixes/5-fix-wm-registration new file mode 100644 index 00000000..513c96eb --- /dev/null +++ b/release-notes/bugfixes/5-fix-wm-registration @@ -0,0 +1 @@ +changed WM registration selection from WM_S_S<screen> to WM_S<screen> diff --git a/release-notes/bugfixes/6-fix-graphics-artifacts b/release-notes/bugfixes/6-fix-graphics-artifacts new file mode 100644 index 00000000..a13e2b0f --- /dev/null +++ b/release-notes/bugfixes/6-fix-graphics-artifacts @@ -0,0 +1 @@ +avoid graphics artifacts when changing the layout tree by initializing surfaces to all black diff --git a/release-notes/bugfixes/7-fix-segfault b/release-notes/bugfixes/7-fix-segfault new file mode 100644 index 00000000..5c280bee --- /dev/null +++ b/release-notes/bugfixes/7-fix-segfault @@ -0,0 +1 @@ +Fix segfault if command in bindsym is empty diff --git a/release-notes/bugfixes/7-update-parent-con-title-on-sibling-move b/release-notes/bugfixes/7-update-parent-con-title-on-sibling-move new file mode 100644 index 00000000..7bd57dfe --- /dev/null +++ b/release-notes/bugfixes/7-update-parent-con-title-on-sibling-move @@ -0,0 +1 @@ +update parent split con titles when child con swaps position with another child con diff --git a/release-notes/bugfixes/8-bar-font b/release-notes/bugfixes/8-bar-font new file mode 100644 index 00000000..618d3c15 --- /dev/null +++ b/release-notes/bugfixes/8-bar-font @@ -0,0 +1 @@ +fix default font not being applied to bars if defined after bar block diff --git a/release-notes/bugfixes/8-bar-output-trailing-whitespace b/release-notes/bugfixes/8-bar-output-trailing-whitespace new file mode 100644 index 00000000..c551d883 --- /dev/null +++ b/release-notes/bugfixes/8-bar-output-trailing-whitespace @@ -0,0 +1 @@ +strip trailing whitespace in bar output names diff --git a/release-notes/bugfixes/8-fix-nested-variables-crash b/release-notes/bugfixes/8-fix-nested-variables-crash new file mode 100644 index 00000000..44058ce0 --- /dev/null +++ b/release-notes/bugfixes/8-fix-nested-variables-crash @@ -0,0 +1 @@ +Fix crash if config contains nested variables. diff --git a/release-notes/bugfixes/8-mode-default-sigsegv b/release-notes/bugfixes/8-mode-default-sigsegv new file mode 100644 index 00000000..75ce814f --- /dev/null +++ b/release-notes/bugfixes/8-mode-default-sigsegv @@ -0,0 +1 @@ +Fix segfault with explicit mode "default" key bindings. diff --git a/release-notes/bugfixes/9-bs-normal b/release-notes/bugfixes/9-bs-normal new file mode 100644 index 00000000..0342fbf0 --- /dev/null +++ b/release-notes/bugfixes/9-bs-normal @@ -0,0 +1 @@ +Restore BS_NORMAL _MOTIF_WM_HINTS correctly diff --git a/release-notes/changes/0-example b/release-notes/changes/0-example new file mode 100644 index 00000000..6242e4ed --- /dev/null +++ b/release-notes/changes/0-example @@ -0,0 +1 @@ +Acquire the WM_Sn selection when starting as required by ICCCM diff --git a/release-notes/changes/1-valid-socket b/release-notes/changes/1-valid-socket new file mode 100644 index 00000000..e605e6dd --- /dev/null +++ b/release-notes/changes/1-valid-socket @@ -0,0 +1 @@ +Refuse to start without valid IPC socket diff --git a/release-notes/changes/2-client.focused_tab_title b/release-notes/changes/2-client.focused_tab_title new file mode 100644 index 00000000..04802192 --- /dev/null +++ b/release-notes/changes/2-client.focused_tab_title @@ -0,0 +1 @@ +Add client.focused_tab_title color option diff --git a/release-notes/changes/3-focus-outputs b/release-notes/changes/3-focus-outputs new file mode 100644 index 00000000..cecd5b42 --- /dev/null +++ b/release-notes/changes/3-focus-outputs @@ -0,0 +1 @@ +Add support for multiple outputs in focus command diff --git a/release-notes/changes/3-tiling-drag b/release-notes/changes/3-tiling-drag new file mode 100644 index 00000000..195fcdbc --- /dev/null +++ b/release-notes/changes/3-tiling-drag @@ -0,0 +1 @@ +Allow moving tiling windows with the mouse diff --git a/release-notes/changes/4-title_window_icon-toggle b/release-notes/changes/4-title_window_icon-toggle new file mode 100644 index 00000000..f15e7a4a --- /dev/null +++ b/release-notes/changes/4-title_window_icon-toggle @@ -0,0 +1 @@ +Add title_window_icon toggle diff --git a/release-notes/generator.pl b/release-notes/generator.pl new file mode 100755 index 00000000..d77eb098 --- /dev/null +++ b/release-notes/generator.pl @@ -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 diff --git a/src/click.c b/src/click.c index 7f8dd278..10af756e 100644 --- a/src/click.c +++ b/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"); diff --git a/src/commands.c b/src/commands.c index 798e2f6c..27853bd0 100644 --- a/src/commands.c +++ b/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; + } - /* 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; + workspace_show(ws); } - workspace_show(ws); - - cmd_output->needs_tree_render = true; - ysuccess(true); + cmd_output->needs_tree_render = success; + if (success) { + ysuccess(true); + } else { + yerror("No output matched"); + } } /* @@ -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. */ diff --git a/src/commands_parser.c b/src/commands_parser.c index fd02293d..7cdf6c68 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -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 ? '^' : ' '); @@ -731,6 +731,41 @@ 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) { + 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"); @@ -1404,6 +1427,20 @@ bool con_move_to_mark(Con *con, const char *mark) { } /* + * 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); diff --git a/src/config.c b/src/config.c index c590f3c5..e2dd69bd 100644 --- a/src/config.c +++ b/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); diff --git a/src/config_directives.c b/src/config_directives.c index 3bb6e793..a611da27 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -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; diff --git a/src/config_parser.c b/src/config_parser.c index 9cba93bc..baf5848a 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -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; } } diff --git a/src/display_version.c b/src/display_version.c index bced4c19..bfdbc8d6 100644 --- a/src/display_version.c +++ b/src/display_version.c @@ -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) diff --git a/src/floating.c b/src/floating.c index f087832d..7888d579 100644 --- a/src/floating.c +++ b/src/floating.c @@ -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}. diff --git a/src/handlers.c b/src/handlers.c index 86859ebc..b0354d1c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -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 @@ -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"); diff --git a/src/manage.c b/src/manage.c index ee046f0f..56a6d0ba 100644 --- a/src/manage.c +++ b/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); @@ -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; } diff --git a/src/randr.c b/src/randr.c index b4d1d094..26b4ca34 100644 --- a/src/randr.c +++ b/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); diff --git a/src/regex.c b/src/regex.c index 8f039157..66ae5113 100644 --- a/src/regex.c +++ b/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; diff --git a/src/render.c b/src/render.c index cf4fd45f..cc1c01f8 100644 --- a/src/render.c +++ b/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", diff --git a/src/tiling_drag.c b/src/tiling_drag.c new file mode 100644 index 00000000..cce91f29 --- /dev/null +++ b/src/tiling_drag.c @@ -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(); +} @@ -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"; +} diff --git a/src/window.c b/src/window.c index 734a3264..7c65ceea 100644 --- a/src/window.c +++ b/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 diff --git a/src/workspace.c b/src/workspace.c index 739358a8..e1ac49d3 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -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); } @@ -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); diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index 9c0b73d4..75cbf835 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -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; } diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 0f5252a6..6d73afca 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -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 ]) diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 48a0dbf6..6345ae68 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -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); diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t index 13633327..99158eab 100644 --- a/testcases/t/171-config-migrate.t +++ b/testcases/t/171-config-migrate.t @@ -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'); diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index a87a7b89..3a21a8a0 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -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 diff --git a/testcases/t/210-mark-unmark.t b/testcases/t/210-mark-unmark.t index 0d9a073b..166ca23f 100644 --- a/testcases/t/210-mark-unmark.t +++ b/testcases/t/210-mark-unmark.t @@ -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 # diff --git a/testcases/t/219-ipc-window-focus.t b/testcases/t/219-ipc-window-focus.t index 696fc7b2..770536f3 100644 --- a/testcases/t/219-ipc-window-focus.t +++ b/testcases/t/219-ipc-window-focus.t @@ -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'); diff --git a/testcases/t/247-config-line-continuation.t b/testcases/t/247-config-line-continuation.t index 8098b998..c54f1ac4 100644 --- a/testcases/t/247-config-line-continuation.t +++ b/testcases/t/247-config-line-continuation.t @@ -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 \ diff --git a/testcases/t/289-ipc-shutdown-event.t b/testcases/t/289-ipc-shutdown-event.t index 606474e2..a56c4ac7 100644 --- a/testcases/t/289-ipc-shutdown-event.t +++ b/testcases/t/289-ipc-shutdown-event.t @@ -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; diff --git a/testcases/t/294-focus-order.t b/testcases/t/294-focus-order.t index ce790019..dfadd0af 100644 --- a/testcases/t/294-focus-order.t +++ b/testcases/t/294-focus-order.t @@ -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) diff --git a/testcases/t/301-shape.t b/testcases/t/301-shape.t index ac0ec5a7..b18ac0ca 100644 --- a/testcases/t/301-shape.t +++ b/testcases/t/301-shape.t @@ -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 }; diff --git a/testcases/t/304-ipc-workspace-init.t b/testcases/t/304-ipc-workspace-init.t index 0eb838dc..4c181f7c 100644 --- a/testcases/t/304-ipc-workspace-init.t +++ b/testcases/t/304-ipc-workspace-init.t @@ -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 diff --git a/testcases/t/314-window-icon-padding.t b/testcases/t/314-window-icon-padding.t index 5320ab9f..71cf5bd5 100644..100755 --- a/testcases/t/314-window-icon-padding.t +++ b/testcases/t/314-window-icon-padding.t @@ -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); ################################################################################ diff --git a/testcases/t/315-long-commands.t b/testcases/t/315-long-commands.t new file mode 100644 index 00000000..3db37dab --- /dev/null +++ b/testcases/t/315-long-commands.t @@ -0,0 +1,46 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • 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; diff --git a/testcases/t/316-drag-container.t b/testcases/t/316-drag-container.t new file mode 100644 index 00000000..7e2b8494 --- /dev/null +++ b/testcases/t/316-drag-container.t @@ -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; diff --git a/testcases/t/316-transient-for-loop.t b/testcases/t/316-transient-for-loop.t new file mode 100644 index 00000000..336a8d8d --- /dev/null +++ b/testcases/t/316-transient-for-loop.t @@ -0,0 +1,42 @@ +#!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 i3 does not get stuck in an endless loop between two windows that +# set transient_for for each other. +# Ticket: #4404 +# Bug still in: 4.20-69-g43e805a00 +# +use i3test i3_config => <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +popup_during_fullscreen smart; +EOT + +my $fs = open_window; +cmd 'fullscreen enable'; + +my $w1 = open_window({ dont_map => 1 }); +my $w2 = open_window({ dont_map => 1 }); + +$w1->transient_for($w2); +$w2->transient_for($w1); +$w1->map; +$w2->map; + +does_i3_live; + +done_testing; diff --git a/testcases/t/317-bar-config-font-order.t b/testcases/t/317-bar-config-font-order.t new file mode 100644 index 00000000..3c1fdcdd --- /dev/null +++ b/testcases/t/317-bar-config-font-order.t @@ -0,0 +1,39 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that bar config blocks get the i3-wide font configured, +# regardless of where the font is configured in the config file +# (before or after the bar config blocks). +# Ticket: #5031 +# Bug still in: 4.20-105-g4db383e4 +use i3test i3_config => <<'EOT'; +# i3 config file (v4) + +bar { + # no font directive here, no i3-wide font configured (yet) +} + +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +EOT + +my $i3 = i3(get_socket_path(0)); +my $bars = $i3->get_bar_config()->recv; + +my $bar_id = shift @$bars; +my $bar_config = $i3->get_bar_config($bar_id)->recv; +is($bar_config->{font}, 'fixed', 'font ok'); + +done_testing; diff --git a/testcases/t/317-bar-output-trailing-space.t b/testcases/t/317-bar-output-trailing-space.t new file mode 100644 index 00000000..540d30c9 --- /dev/null +++ b/testcases/t/317-bar-output-trailing-space.t @@ -0,0 +1,71 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that any trailing whitespace in strings (including in +# “bar { output <output> }” in particular) is stripped. +# Ticket: #5064 +# Bug still in: 4.20-105-g4db383e4 +use i3test i3_autostart => 0; + +# Test with a single output. + +my $config = <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +bar { + output anything +} +EOT + +my $pid = launch_with_config($config); + +my $i3 = i3(get_socket_path(0)); +my $bars = $i3->get_bar_config()->recv; +is(@$bars, 1, 'one bar configured'); +my $bar_id = shift @$bars; + +my $bar_config = $i3->get_bar_config($bar_id)->recv; +is_deeply($bar_config->{outputs}, [ 'anything' ], 'outputs do not have trailing whitespace'); + +exit_gracefully($pid); + +# Test with multiple outputs for a single bar. + +$config = <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +bar { + output nospace + output singlespace +} +EOT + +$pid = launch_with_config($config); + +$i3 = i3(get_socket_path(0)); +$bars = $i3->get_bar_config()->recv; +is(@$bars, 1, 'one bar configured'); +$bar_id = shift @$bars; + +$bar_config = $i3->get_bar_config($bar_id)->recv; +is_deeply($bar_config->{outputs}, [ 'nospace', 'singlespace' ], 'outputs do not have trailing whitespace'); + +exit_gracefully($pid); + + +done_testing; diff --git a/testcases/t/502-focus-output.t b/testcases/t/502-focus-output.t index 118aba16..5b051de4 100644 --- a/testcases/t/502-focus-output.t +++ b/testcases/t/502-focus-output.t @@ -31,13 +31,6 @@ my $i3 = i3(get_socket_path()); # use 'focus output' and verify that focus gets changed appropriately ################################################################################ -sub focused_output { - my $tree = $i3->get_tree->recv; - my $focused = $tree->{focus}->[0]; - my $output = first { $_->{id} == $focused } @{$tree->{nodes}}; - return $output->{name}; -} - sync_with_i3; $x->root->warp_pointer(0, 0); sync_with_i3; diff --git a/testcases/t/506-focus-right.t b/testcases/t/506-focus-right.t index 871bc1c8..2d8ed205 100644 --- a/testcases/t/506-focus-right.t +++ b/testcases/t/506-focus-right.t @@ -149,32 +149,25 @@ sync_with_i3; # Ensure that focusing right/left works in the expected order. ############################################################################ -sub get_focused_output { - my $tree = i3(get_socket_path())->get_tree->recv; - my ($focused_id) = @{$tree->{focus}}; - my ($output) = grep { $_->{id} == $focused_id } @{$tree->{nodes}}; - return $output->{name}; -} - -is(get_focused_output(), 'fake-0', 'focus on fake-0'); +is(focused_output, 'fake-0', 'focus on fake-0'); cmd 'focus output right'; -is(get_focused_output(), 'fake-1', 'focus on fake-1'); +is(focused_output, 'fake-1', 'focus on fake-1'); cmd 'focus output right'; -is(get_focused_output(), 'fake-2', 'focus on fake-2'); +is(focused_output, 'fake-2', 'focus on fake-2'); cmd 'focus output left'; -is(get_focused_output(), 'fake-1', 'focus on fake-1'); +is(focused_output, 'fake-1', 'focus on fake-1'); cmd 'focus output left'; -is(get_focused_output(), 'fake-0', 'focus on fake-0'); +is(focused_output, 'fake-0', 'focus on fake-0'); cmd 'focus output left'; -is(get_focused_output(), 'fake-2', 'focus on fake-2 (wrapping)'); +is(focused_output, 'fake-2', 'focus on fake-2 (wrapping)'); cmd 'focus output right'; -is(get_focused_output(), 'fake-0', 'focus on fake-0 (wrapping)'); +is(focused_output, 'fake-0', 'focus on fake-0 (wrapping)'); exit_gracefully($pid); diff --git a/testcases/t/531-fullscreen-on-given-output.t b/testcases/t/531-fullscreen-on-given-output.t index fb14b7a0..2459f06c 100644 --- a/testcases/t/531-fullscreen-on-given-output.t +++ b/testcases/t/531-fullscreen-on-given-output.t @@ -55,7 +55,7 @@ sync_with_i3; # Check that the windows are on the correct output is_deeply(scalar $win_on_first_output->rect, $orig_rect1, "first window spans the first output"); -is_deeply(scalar $win_on_second_output->rect, $orig_rect2, "second window spans the sencond output"); +is_deeply(scalar $win_on_second_output->rect, $orig_rect2, "second window spans the second output"); # Check that both windows remained fullscreen my $tree = i3(get_socket_path())->get_tree->recv; diff --git a/testcases/t/543-move-workspace-to-multiple-outputs.t b/testcases/t/543-move-workspace-to-multiple-outputs.t index b9bfabec..7b0f8c2b 100644 --- a/testcases/t/543-move-workspace-to-multiple-outputs.t +++ b/testcases/t/543-move-workspace-to-multiple-outputs.t @@ -14,7 +14,7 @@ # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf # (unless you are already familiar with Perl) # -# Test using multiple workspaces for 'move workspace to output …' +# Test using multiple outputs for 'move workspace to output …' # Ticket: #4337 use i3test i3_config => <<EOT; # i3 config file (v4) @@ -40,6 +40,16 @@ sub is_ws { } ############################################################################### +# Test moving workspace to same output +# See issue #4691 +############################################################################### +is_ws(1, 0, 'sanity check'); + +my $reply = cmd '[con_mark=aa] move workspace to output fake-0'; +is_ws(1, 0, 'workspace did not move'); +ok($reply->[0]->{success}, 'reply success'); + +############################################################################### # Test using "next" special keyword ############################################################################### @@ -56,7 +66,7 @@ for (my $i = 1; $i < 9; $i++) { } ############################################################################### -# Same as above but explicitely type all the outputs +# Same as above but explicitly type all the outputs ############################################################################### is_ws(1, 0, 'sanity check'); diff --git a/testcases/t/544-focus-multiple-outputs.t b/testcases/t/544-focus-multiple-outputs.t new file mode 100644 index 00000000..504b8d11 --- /dev/null +++ b/testcases/t/544-focus-multiple-outputs.t @@ -0,0 +1,67 @@ +#!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 using multiple output for 'focus output …' +# Ticket: #4619 +use i3test i3_config => <<EOT; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+0+768,1024x768+1024+768 +EOT + +############################################################################### +# Test using "next" special keyword +############################################################################### + +is(focused_output, "fake-0", 'sanity check'); + +for (my $i = 1; $i < 9; $i++) { + cmd 'focus output next'; + + my $out = $i % 4; + is(focused_output, "fake-$out", 'focus output next cycle'); +} + +############################################################################### +# Same as above but explicitly type all the outputs +############################################################################### + +is(focused_output, "fake-0", 'sanity check'); + +for (my $i = 1; $i < 10; $i++) { + cmd 'focus output fake-0 fake-1 fake-2 fake-3'; + + my $out = $i % 4; + is(focused_output, "fake-$out", 'focus output next cycle'); +} + +############################################################################### +# Use a subset of the outputs plus some non-existing outputs +############################################################################### + +cmd 'focus output fake-1'; +is(focused_output, "fake-1", 'start from fake-1 which is not included in output list'); + +my @order = (0, 3, 2); +for (my $i = 0; $i < 10; $i++) { + cmd 'focus output doesnotexist fake-0 alsodoesnotexist fake-3 fake-2'; + + my $out = $order[$i % 3]; + is(focused_output, "fake-$out", 'focus output next cycle'); +} + +done_testing; diff --git a/testcases/t/545-i3-registration.t b/testcases/t/545-i3-registration.t new file mode 100644 index 00000000..6c8d68f4 --- /dev/null +++ b/testcases/t/545-i3-registration.t @@ -0,0 +1,11 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests whether our WM registration is done with the correct WM_S0 selection. + +use i3test; + +my $x = X11::XCB::Connection->new; +my $reply = $x->get_selection_owner($x->atom(name => 'WM_S0')->id); +ok($reply, "registration successful"); +done_testing; diff --git a/testcases/t/546-empty-bindcommand.t b/testcases/t/546-empty-bindcommand.t new file mode 100644 index 00000000..dae26293 --- /dev/null +++ b/testcases/t/546-empty-bindcommand.t @@ -0,0 +1,31 @@ +#!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 i3 doesn't crash if the binding command is empty. +# Ticket: #5000 + +use i3test i3_autostart => 0; + +my $config = <<EOT; +# i3 config file (v4) +bindsym X +EOT + +my $pid = launch_with_config($config); +does_i3_live; + +exit_gracefully($pid); +done_testing; diff --git a/testcases/t/547-explicit-mode-default.t b/testcases/t/547-explicit-mode-default.t new file mode 100644 index 00000000..a966c7f6 --- /dev/null +++ b/testcases/t/547-explicit-mode-default.t @@ -0,0 +1,34 @@ +#!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 explicitly defined default mode doesn't cause segfault. +# Ticket: #5006 +# Bug still in: 4.20-96-gce2665ca + +use i3test i3_autostart => 0; + +my $config = <<EOT; +# i3 config file (v4) +mode "default" { + bindsym X resize +} +EOT + +my $pid = launch_with_config($config); +does_i3_live; + +exit_gracefully($pid); +done_testing; diff --git a/testcases/t/547-nested-variables.t b/testcases/t/547-nested-variables.t new file mode 100644 index 00000000..990a42e9 --- /dev/null +++ b/testcases/t/547-nested-variables.t @@ -0,0 +1,81 @@ +#!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 i3 doesn't crash if the config contains nested variables. +# Ticket: #5002 + +use i3test i3_autostart => 0; + +####################################################################### +# Test calloc crash +####################################################################### + +my $config = <<'EOT'; +# i3 config file (v4) +set $long_variable_name_with_short_value 1 +set $$long_variable_name_with_short_value 2 +set $$$long_variable_name_with_short_value 3 +EOT + +my $pid = launch_with_config($config); + +# ==2108678==ERROR: AddressSanitizer: requested allocation size 0xffffffffffffffe1 (0x7e8 after adjustments for alignment, red zones etc.) exceeds maximum supported size of 0x10000000000 (thread T0) +# #0 0x7feaa9cbf411 in __interceptor_calloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:77 +# #1 0x560867c0c13d in scalloc ../libi3/safewrappers.c:29 +# #2 0x560867b7bd63 in parse_file ../src/config_parser.c:1030 +# #3 0x560867b6b1b8 in load_configuration ../src/config.c:261 +# #4 0x560867baf9cf in main ../src/main.c:677 +# #5 0x7feaa95b52cf (/usr/lib/libc.so.6+0x232cf) + +does_i3_live; + +exit_gracefully($pid); + + +####################################################################### +# Test buffer overflow +####################################################################### + +$config = <<'EOT'; +# i3 config file (v4) +set $x 1 +set $$x 2 +EOT + +$pid = launch_with_config($config); + +# ==2110007==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6070000014f2 at pc 0x558590f680ba bp 0x7ffced72b760 sp 0x7ffced72b750 +# WRITE of size 1 at 0x6070000014f2 thread T0 +# #0 0x558590f680b9 in parse_file ../src/config_parser.c:1051 +# #1 0x558590f571b8 in load_configuration ../src/config.c:261 +# #2 0x558590f9b9cf in main ../src/main.c:677 +# #3 0x7f81c61c82cf (/usr/lib/libc.so.6+0x232cf) +# #4 0x7f81c61c8389 in __libc_start_main (/usr/lib/libc.so.6+0x23389) +# #5 0x558590f0bd54 in _start ../sysdeps/x86_64/start.S:115 + +# 0x6070000014f2 is located 0 bytes to the right of 66-byte region [0x6070000014b0,0x6070000014f2) +# allocated by thread T0 here: +# #0 0x7f81c68bf411 in __interceptor_calloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:77 +# #1 0x558590ff813d in scalloc ../libi3/safewrappers.c:29 +# #2 0x558590f67d63 in parse_file ../src/config_parser.c:1030 +# #3 0x558590f571b8 in load_configuration ../src/config.c:261 +# #4 0x558590f9b9cf in main ../src/main.c:677 +# #5 0x7f81c61c82cf (/usr/lib/libc.so.6+0x232cf) + +does_i3_live; + +exit_gracefully($pid); +done_testing; diff --git a/travis/check-formatting.sh b/travis/check-formatting.sh deleted file mode 100755 index 3424513f..00000000 --- a/travis/check-formatting.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -set -e -set -x - -clang-format-9 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) diff --git a/travis/check-spelling.pl b/travis/check-spelling.pl index 6357e78a..52f84c5b 100755 --- a/travis/check-spelling.pl +++ b/travis/check-spelling.pl @@ -26,11 +26,12 @@ my $exitcode = 0; # Whitelist for spelling errors in manpages, in case the spell checker has # false-positives. -my $binary_spelling_exceptions = { - #'exmaple' => 1, # Example for how to add entries to this whitelist. - 'betwen' => 1, # asan_flags.inc contains this spelling error. - 'dissassemble' => 1, # https://reviews.llvm.org/D93902 -}; +my $binary_spelling_exceptions = [ + #'exmaple', # Example for how to add entries to this whitelist. + 'betwen', # asan_flags.inc contains this spelling error. + 'dissassemble', # https://reviews.llvm.org/D93902 + 'oT', # lintian finds this in build/i3bar when built with clang?! +]; my @binaries = qw( build/i3 build/i3-config-wizard @@ -41,7 +42,7 @@ my @binaries = qw( build/i3bar ); for my $binary (@binaries) { - check_spelling($profile, slurp($binary), $binary_spelling_exceptions, sub { + check_spelling($profile->data, slurp($binary), $binary_spelling_exceptions, sub { my ($current, $fixed) = @_; say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|; $exitcode = 1; @@ -50,13 +51,13 @@ for my $binary (@binaries) { # Whitelist for spelling errors in manpages, in case the spell checker has # false-positives. -my $manpage_spelling_exceptions = { -}; +my $manpage_spelling_exceptions = [ +]; for my $name (glob('build/man/*.1')) { for my $line (split(/\n/, slurp($name))) { next if $line =~ /^\.\\\"/o; - check_spelling($profile, $line, $manpage_spelling_exceptions, sub { + check_spelling($profile->data, $line, $manpage_spelling_exceptions, sub { my ($current, $fixed) = @_; say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|; $exitcode = 1; diff --git a/travis/cleanup-bintray.pl b/travis/cleanup-bintray.pl deleted file mode 100755 index e89efb1b..00000000 --- a/travis/cleanup-bintray.pl +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env perl -# vim:ts=4:sw=4:expandtab - -use strict; -use warnings; -use Data::Dumper; -use HTTP::Tiny; # in core since v5.13.9 -use JSON::PP; # in core since v5.13.9 -use MIME::Base64; # in core since v5.7 -use v5.13; - -my $repo = shift; - -my $auth = $ENV{'BINTRAY_USER'} . ':' . $ENV{'BINTRAY_KEY'}; -die "BINTRAY_USER and/or BINTRAY_KEY environment variables not set" if $auth eq ':'; -# TODO(stapelberg): switch to putting $auth into the URL once perl-modules ≥ -# 5.20 is available on travis (Ubuntu Wily or newer). -my $auth_header = 'Basic ' . MIME::Base64::encode_base64($auth, ""); -my $apiurl = 'https://api.bintray.com/packages/i3/' . $repo . '/i3-wm'; -my $client = HTTP::Tiny->new( - verify_SSL => 1, - default_headers => { - 'authorization' => $auth_header, - }); -my $resp = $client->get($apiurl); -die "Getting versions failed: HTTP status $resp->{status} (content: $resp->{content})" unless $resp->{success}; -my $decoded = decode_json($resp->{content}); -my @versions = reverse sort { - (system("/usr/bin/dpkg", "--compare-versions", "$a", "gt", "$b") == 0) ? 1 : -1 -} @{$decoded->{versions}}; - -# Keep the most recent 5 versions. -splice(@versions, 0, 5); - -for my $version (@versions) { - say "Deleting old version $version"; - $resp = $client->request('DELETE', "$apiurl/versions/$version"); - die "Deletion of version $version failed: HTTP status $resp->{status} (content: $resp->{content})" unless $resp->{success}; -} diff --git a/travis/travis-base-386.Dockerfile b/travis/travis-base-386.Dockerfile index fc035034..8996e029 100644 --- a/travis/travis-base-386.Dockerfile +++ b/travis/travis-base-386.Dockerfile @@ -13,12 +13,11 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-9 (for checking formatting and building with clang), # lintian (for checking spelling errors), RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - build-essential clang clang-format-9 \ + build-essential clang \ lintian && \ rm -rf /var/lib/apt/lists/* @@ -27,3 +26,8 @@ COPY debian/control /usr/src/i3-debian-packaging/control RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ rm -rf /var/lib/apt/lists/* + +# The user outside of Docker (GitHub Actions CI runner) and inside of Docker +# (root) are different, and newer versions of git error out in that scenario. +# To fix this, explicitly configure /usr/src/i3 as a safe directory: +RUN git config --global --add safe.directory /usr/src/i3 diff --git a/travis/travis-base-ubuntu-386.Dockerfile b/travis/travis-base-ubuntu-386.Dockerfile index 747330aa..4a41fe3e 100644 --- a/travis/travis-base-ubuntu-386.Dockerfile +++ b/travis/travis-base-ubuntu-386.Dockerfile @@ -13,12 +13,11 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-9 (for checking formatting and building with clang), # lintian (for checking spelling errors), RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - build-essential clang clang-format-9 \ + build-essential clang \ lintian && \ rm -rf /var/lib/apt/lists/* @@ -27,3 +26,8 @@ COPY debian/control /usr/src/i3-debian-packaging/control RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ rm -rf /var/lib/apt/lists/* + +# The user outside of Docker (GitHub Actions CI runner) and inside of Docker +# (root) are different, and newer versions of git error out in that scenario. +# To fix this, explicitly configure /usr/src/i3 as a safe directory: +RUN git config --global --add safe.directory /usr/src/i3 diff --git a/travis/travis-base-ubuntu.Dockerfile b/travis/travis-base-ubuntu.Dockerfile index 8a728af3..8523b92c 100644 --- a/travis/travis-base-ubuntu.Dockerfile +++ b/travis/travis-base-ubuntu.Dockerfile @@ -13,13 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-9 (for checking formatting and building with clang), # lintian (for checking spelling errors), # test suite dependencies (for running tests) RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - build-essential clang clang-format-9 \ + build-essential clang \ lintian && \ rm -rf /var/lib/apt/lists/* @@ -28,3 +27,8 @@ COPY debian/control /usr/src/i3-debian-packaging/control RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ rm -rf /var/lib/apt/lists/* + +# The user outside of Docker (GitHub Actions CI runner) and inside of Docker +# (root) are different, and newer versions of git error out in that scenario. +# To fix this, explicitly configure /usr/src/i3 as a safe directory: +RUN git config --global --add safe.directory /usr/src/i3 diff --git a/travis/travis-base.Dockerfile b/travis/travis-base.Dockerfile index e5552c14..52ae0656 100644 --- a/travis/travis-base.Dockerfile +++ b/travis/travis-base.Dockerfile @@ -11,13 +11,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-9 (for checking formatting and building with clang), # lintian (for checking spelling errors), # test suite dependencies (for running tests) RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - build-essential clang clang-format-9 \ + build-essential clang \ lintian \ libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl x11-xserver-utils && \ rm -rf /var/lib/apt/lists/* @@ -28,3 +27,8 @@ COPY debian/changelog /usr/src/i3-debian-packaging/changelog RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ rm -rf /var/lib/apt/lists/* + +# The user outside of Docker (GitHub Actions CI runner) and inside of Docker +# (root) are different, and newer versions of git error out in that scenario. +# To fix this, explicitly configure /usr/src/i3 as a safe directory: +RUN git config --global --add safe.directory /usr/src/i3 |