From d76b7fab4538df10a5325aa508ee2b2fa92ccf47 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 30 Sep 2015 08:55:24 +0200 Subject: [PATCH 001/123] Update debian/changelog --- debian/changelog | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index 65b7fc10..aaa7e54e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,8 @@ -i3-wm (4.10.5-1) unstable; urgency=medium +i3-wm (4.11-1) unstable; urgency=medium - * UNRELEASED + * New upstream release. - -- Michael Stapelberg Tue, 08 Sep 2015 09:25:45 +0200 + -- Michael Stapelberg Wed, 30 Sep 2015 08:50:13 +0200 i3-wm (4.10.4-1) unstable; urgency=medium From d584d0f2db46888aabac0850c86104a622c9b2fc Mon Sep 17 00:00:00 2001 From: Jakob Haufe Date: Thu, 1 Oct 2015 21:34:16 +0200 Subject: [PATCH 002/123] Fix formatting of description list --- docs/userguide | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 13dae4fe..b077eb66 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2134,10 +2134,10 @@ and the following placeholders which will be replaced: +%title+:: The X11 window title (_NET_WM_NAME or WM_NAME as fallback). -+%class+: ++%class+:: The X11 window class (second part of WM_CLASS). This corresponds to the +class+ criterion, see <>. -+%instance+: ++%instance+:: The X11 window instance (first part of WM_CLASS). This corresponds to the +instance+ criterion, see <>. From 62bb7af5c31a42469cc95554eae5dc3db81836cd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Oct 2015 20:23:07 +0200 Subject: [PATCH 003/123] Bugfix: add keymap fall back (_XKB_RULES_NAMES, then defaults) fixes #1983 --- src/bindings.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++--- src/main.c | 2 + 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 32aac05a..b96c401c 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -626,6 +626,77 @@ CommandResult *run_binding(Binding *bind, Con *con) { return result; } +static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { + xcb_intern_atom_reply_t *atom_reply; + size_t content_max_words = 256; + + xcb_window_t root = root_screen->root; + + atom_reply = xcb_intern_atom_reply( + conn, xcb_intern_atom(conn, 0, strlen("_XKB_RULES_NAMES"), "_XKB_RULES_NAMES"), NULL); + if (atom_reply == NULL) + return -1; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL) { + free(atom_reply); + return -1; + } + if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) { + /* We received an incomplete value. Ask again but with a properly + * adjusted size. */ + content_max_words += ceil(prop_reply->bytes_after / 4.0); + /* Repeat the request, with adjusted size */ + free(prop_reply); + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL) { + free(atom_reply); + return -1; + } + } + if (xcb_get_property_value_length(prop_reply) == 0) { + free(atom_reply); + free(prop_reply); + return -1; + } + + const char *walk = (const char *)xcb_get_property_value(prop_reply); + int remaining = xcb_get_property_value_length(prop_reply); + for (int i = 0; i < 5 && remaining > 0; i++) { + const int len = strnlen(walk, remaining); + remaining -= len; + switch (i) { + case 0: + asprintf((char **)&(xkb_names->rules), "%.*s", len, walk); + break; + case 1: + asprintf((char **)&(xkb_names->model), "%.*s", len, walk); + break; + case 2: + asprintf((char **)&(xkb_names->layout), "%.*s", len, walk); + break; + case 3: + asprintf((char **)&(xkb_names->variant), "%.*s", len, walk); + break; + case 4: + asprintf((char **)&(xkb_names->options), "%.*s", len, walk); + break; + } + DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk); + walk += (len + 1); + } + + free(atom_reply); + free(prop_reply); + return 0; +} + /* * Loads the XKB keymap from the X11 server and feeds it to xkbcommon. * @@ -638,12 +709,40 @@ bool load_keymap(void) { } } - struct xkb_keymap *new_keymap; - const int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn); - DLOG("device_id = %d\n", device_id); - if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { - ELOG("xkb_x11_keymap_new_from_device failed\n"); - return false; + struct xkb_keymap *new_keymap = NULL; + int32_t device_id; + if (xkb_supported && (device_id = xkb_x11_get_core_keyboard_device_id(conn)) > -1) { + if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { + ELOG("xkb_x11_keymap_new_from_device failed\n"); + return false; + } + } else { + /* Likely there is no XKB support on this server, possibly because it + * is a VNC server. */ + LOG("No XKB / core keyboard device? Assembling keymap from local RMLVO.\n"); + struct xkb_rule_names names = { + .rules = NULL, + .model = NULL, + .layout = NULL, + .variant = NULL, + .options = NULL}; + if (fill_rmlvo_from_root(&names) == -1) { + ELOG("Could not get _XKB_RULES_NAMES atom from root window, falling back to defaults.\n"); + if ((new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0)) == NULL) { + ELOG("xkb_keymap_new_from_names(NULL) failed\n"); + return false; + } + } + new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0); + free((char *)names.rules); + free((char *)names.model); + free((char *)names.layout); + free((char *)names.variant); + free((char *)names.options); + if (new_keymap == NULL) { + ELOG("xkb_keymap_new_from_names(RMLVO) failed\n"); + return false; + } } xkb_keymap_unref(xkb_keymap); xkb_keymap = new_keymap; diff --git a/src/main.c b/src/main.c index 0dc25936..563fb00c 100644 --- a/src/main.c +++ b/src/main.c @@ -87,6 +87,7 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment /* We hope that those are supported and set them to true */ bool xcursor_supported = true; +bool xkb_supported = true; /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. @@ -543,6 +544,7 @@ int main(int argc, char *argv[]) { const xcb_query_extension_reply_t *extreply; extreply = xcb_get_extension_data(conn, &xcb_xkb_id); + xkb_supported = extreply->present; if (!extreply->present) { DLOG("xkb is not present on this server\n"); } else { From 94bdf607bb54b57729a4a4453c94fb11054b6330 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Oct 2015 20:42:52 +0200 Subject: [PATCH 004/123] Use sasprintf() --- src/bindings.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index b96c401c..b9c7d914 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -673,19 +673,19 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { remaining -= len; switch (i) { case 0: - asprintf((char **)&(xkb_names->rules), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk); break; case 1: - asprintf((char **)&(xkb_names->model), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->model), "%.*s", len, walk); break; case 2: - asprintf((char **)&(xkb_names->layout), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->layout), "%.*s", len, walk); break; case 3: - asprintf((char **)&(xkb_names->variant), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->variant), "%.*s", len, walk); break; case 4: - asprintf((char **)&(xkb_names->options), "%.*s", len, walk); + sasprintf((char **)&(xkb_names->options), "%.*s", len, walk); break; } DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk); From bfd9960df254c5bc29d1f7fd42298ec857c8484f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 12 Oct 2015 12:56:19 +0200 Subject: [PATCH 005/123] Suppress no_focus for first window on a workspace. With this patch, the no_focus directive will be ignored if the to-be-opened window is the first on its workspace as there's no reason the user would not want to focus it in this case. This improves usability when, for example, using a tabbed workspace_layout. fixes #1987 --- docs/userguide | 4 ++++ src/manage.c | 18 ++++++++++++++---- testcases/t/242-no-focus.t | 30 ++++++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/docs/userguide b/docs/userguide index b077eb66..41af30a1 100644 --- a/docs/userguide +++ b/docs/userguide @@ -610,6 +610,10 @@ Note that this does not apply to all cases, e.g., when feeding data into a runni causing it to request being focused. To configure the behavior in such cases, refer to <>. ++no_focus+ will also be ignored for the first window on a workspace as there shouldn't be +a reason to not focus the window in this case. This allows for better usability in +combination with +workspace_layout+. + *Syntax*: ------------------- no_focus diff --git a/src/manage.c b/src/manage.c index e3769670..5cfe490e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -524,13 +524,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* Send an event about window creation */ ipc_send_window_event("new", nc); + if (set_focus && assignment_for(cwindow, A_NO_FOCUS) != NULL) { + /* The first window on a workspace should always be focused. We have to + * compare with == 1 because the container has already been inserted at + * this point. */ + if (con_num_children(ws) == 1) { + DLOG("This is the first window on this workspace, ignoring no_focus.\n"); + } else { + DLOG("no_focus was set for con = %p, not setting focus.\n", nc); + set_focus = false; + } + } + /* Defer setting focus after the 'new' event has been sent to ensure the * proper window event sequence. */ if (set_focus && !nc->window->doesnt_accept_focus && nc->mapped) { - if (assignment_for(cwindow, A_NO_FOCUS) == NULL) { - DLOG("Now setting focus.\n"); - con_focus(nc); - } + DLOG("Now setting focus.\n"); + con_focus(nc); } tree_render(); diff --git a/testcases/t/242-no-focus.t b/testcases/t/242-no-focus.t index 143ae5cf..0a7f5c93 100644 --- a/testcases/t/242-no-focus.t +++ b/testcases/t/242-no-focus.t @@ -30,13 +30,15 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 EOT $pid = launch_with_config($config); - + $ws = fresh_workspace; $first = open_window; $focused = get_focused($ws); $second = open_window; +sync_with_i3; isnt(get_focused($ws), $focused, 'focus has changed'); +is($x->input_focus, $second->id, 'input focus has changed'); exit_gracefully($pid); @@ -53,13 +55,37 @@ no_focus [instance=notme] EOT $pid = launch_with_config($config); - + $ws = fresh_workspace; $first = open_window; $focused = get_focused($ws); $second = open_window(wm_class => 'notme'); +sync_with_i3; is(get_focused($ws), $focused, 'focus has not changed'); +is($x->input_focus, $first->id, 'input focus has not changed'); + +exit_gracefully($pid); + +##################################################################### +## 3: no_focus doesn't affect the first window opened on a workspace +##################################################################### + +$config = < 'focusme'); + +sync_with_i3; +is($x->input_focus, $first->id, 'input focus has changed'); exit_gracefully($pid); From ab9d74b434c22a860a3073ad9c7f39aec40b243d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Fri, 16 Oct 2015 21:07:16 +0200 Subject: [PATCH 006/123] Fix moving windows to a marked workspace by mark. When a window is moved to a mark and the marked container is a workspace, we can skip any other logic and just call con_move_to_workspace directly. fixes #2003 --- src/con.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/con.c b/src/con.c index d1148301..5b0cc6c8 100644 --- a/src/con.c +++ b/src/con.c @@ -1020,6 +1020,12 @@ bool con_move_to_mark(Con *con, const char *mark) { return true; } + if (con->type == CT_WORKSPACE) { + DLOG("target container is a workspace, simply moving the container there.\n"); + con_move_to_workspace(con, target, true, false, false); + return true; + } + /* For split containers, we use the currently focused container within it. * This allows setting marks on, e.g., tabbed containers which will move * con to a new tab behind the focused tab. */ From 0a16a4b5e5e37a83cb313a376759059281080aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sat, 17 Oct 2015 19:59:45 +0200 Subject: [PATCH 007/123] Add proper documentation for binding modes. fixes #1996 --- docs/userguide | 106 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/docs/userguide b/docs/userguide index 41af30a1..7c4a4cb7 100644 --- a/docs/userguide +++ b/docs/userguide @@ -149,8 +149,10 @@ it does not yet exist. The easiest way to resize a container is by using the mouse: Grab the border and move it to the wanted size. -See <> for how to configure i3 to be able to resize -columns/rows with your keyboard. +You can also use <> to define a mode for resizing via the +keyboard. To see an example for this, look at the +https://github.com/i3/i3/blob/next/i3.config.keycodes[default config] provided +by i3. === Restarting i3 inplace @@ -177,7 +179,8 @@ around. By grabbing the borders and moving them you can resize the window. You can also do that by using the <>. Another way to resize floating windows using the mouse is to right-click on the titlebar and drag. -For resizing floating windows with your keyboard, see <>. +For resizing floating windows with your keyboard, see the resizing binding mode +provided by the i3 https://github.com/i3/i3/blob/next/i3.config.keycodes[default config]. Floating windows are always on top of tiling windows. @@ -441,6 +444,59 @@ bindsym button9 move left bindsym button8 move right -------------------------------- +[[binding_modes]] + +=== Binding modes + +You can have multiple sets of bindings by using different binding modes. When +you switch to another binding mode, all bindings from the current mode are +released and only the bindings defined in the new mode are valid for as long as +you stay in that binding mode. The only predefined binding mode is +default+, +which is the mode i3 starts out with and to which all bindings not defined in a +specific binding mode belong. + +Working with binding modes consists of two parts: defining a binding mode and +switching to it. For these purposes, there are one config directive and one +command, both of which are called +mode+. The directive is used to define the +bindings belonging to a certain binding mode, while the command will switch to +the specified mode. + +It is recommended to use binding modes in combination with <> in +order to make maintenance easier. Below is an example of how to use a binding +mode. + +Note that it is advisable to define bindings for switching back to the default +mode. + +Note that it is possible to use <> for binding modes, but you +need to enable it explicitly by passing the +--pango_markup+ flag to the mode +definition. + +*Syntax*: +---------------------------- +# config directive +mode [--pango_markup] + +# command +mode +---------------------------- + +*Example*: +------------------------------------------------------------------------ +# Press $mod+o followed by either f, t, Esc or Return to launch firefox, +# thunderbird or return to the default mode, respectively. +set $mode_launcher Launch: [f]irefox [t]hunderbird +bindsym $mod+o mode "$mode_launcher" + +mode "$mode_launcher" { + bindsym f exec firefox + bindsym t exec thunderbird + + bindsym Esc mode "default" + bindsym Return mode "default" +} +------------------------------------------------------------------------ + [[floating_modifier]] === The floating modifier @@ -624,6 +680,8 @@ no_focus no_focus [window_role="pop-up"] ------------------------------- +[[variables]] + === Variables As you learned in the section about keyboard bindings, you will have @@ -1455,8 +1513,8 @@ bar { Specifies whether the current binding mode indicator should be shown or not. This is useful if you want to hide the workspace buttons but still be able -to see the current binding mode indicator. -For an example of a +mode+ definition, see <>. +to see the current binding mode indicator. See <> to learn what +modes are and how to use them. The default is to show the mode indicator. @@ -2019,38 +2077,12 @@ many percentage points a *tiling container* should be grown or shrunk (the default is 10 percentage points). Note that +resize set+ will only work for floating containers. -I recommend using the resize command inside a so called +mode+: +It is recommended to define bindings for resizing in a dedicated binding mode. +See <> and the example in the i3 +https://github.com/i3/i3/blob/next/i3.config.keycodes[default config] for more +context. -.Example: Configuration file, defining a mode for resizing ----------------------------------------------------------------------- -mode "resize" { - # These bindings trigger as soon as you enter the resize mode - - # Pressing left will shrink the window’s width. - # Pressing right will grow the window’s width. - # Pressing up will shrink the window’s height. - # Pressing down will grow the window’s height. - bindsym j resize shrink width 10 px or 10 ppt - bindsym k resize grow height 10 px or 10 ppt - bindsym l resize shrink height 10 px or 10 ppt - bindsym semicolon resize grow width 10 px or 10 ppt - - # same bindings, but for the arrow keys - bindsym Left resize shrink width 10 px or 10 ppt - bindsym Down resize grow height 10 px or 10 ppt - bindsym Up resize shrink height 10 px or 10 ppt - bindsym Right resize grow width 10 px or 10 ppt - - # back to normal: Enter or Escape - bindsym Return mode "default" - bindsym Escape mode "default" -} - -# Enter resize mode -bindsym $mod+r mode "resize" ----------------------------------------------------------------------- - -*Example 2 - setting urxvt size to 640x480:* +*Example*: ------------------------------------------------ for_window [class="urxvt"] resize set 640 480 ------------------------------------------------ @@ -2128,6 +2160,8 @@ Alternatively, if you do not want to mess with +i3-input+, you could create seperate bindings for a specific set of labels and then only use those labels. /////////////////////////////////////////////////////////////////// +[[pango_markup]] + === Window title format By default, i3 will simply print the X11 window title. Using +title_format+, From 5049990284f039832d67159e67466e35acc2f2e7 Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Mon, 19 Oct 2015 07:39:59 +0200 Subject: [PATCH 008/123] Fix erroneous headline for moving to mark The headline indicated that it would be possible to move containers and *workspaces* to marks but the following text clearly shows that it should state containers and *windows*. --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 7c4a4cb7..2a2ce869 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2035,7 +2035,7 @@ bindsym $mod+x move workspace to output right bindsym $mod+x move container to output VGA1 -------------------------------------------------------- -=== Moving containers/workspaces to marks +=== Moving containers/windows to marks To move a container to another container with a specific mark (see <>), you can use the following command. From f5f5683fa770d191de2cac30178f45755031a8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 16:32:40 +0200 Subject: [PATCH 009/123] Allow the commands parser to use "number" arguments by making the stack typed. --- src/commands_parser.c | 127 ++++++++++++++++++++++++------------------ src/config_parser.c | 51 +---------------- 2 files changed, 76 insertions(+), 102 deletions(-) diff --git a/src/commands_parser.c b/src/commands_parser.c index ffe416f0..d311fdd1 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -73,7 +73,14 @@ typedef struct tokenptr { struct stack_entry { /* Just a pointer, not dynamically allocated. */ const char *identifier; - char *str; + enum { + STACK_STR = 0, + STACK_LONG = 1, + } type; + union { + char *str; + long num; + } val; }; /* 10 entries should be enough for everybody. */ @@ -90,7 +97,30 @@ static void push_string(const char *identifier, char *str) { continue; /* Found a free slot, let’s store it here. */ stack[c].identifier = identifier; - stack[c].str = str; + stack[c].val.str = str; + stack[c].type = STACK_STR; + return; + } + + /* When we arrive here, the stack is full. This should not happen and + * means there’s either a bug in this parser or the specification + * contains a command with more than 10 identified tokens. */ + fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + "in the code, or a new command which contains more than " + "10 identified tokens.\n"); + exit(1); +} + +// TODO move to a common util +static void push_long(const char *identifier, long num) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier != NULL) { + continue; + } + + stack[c].identifier = identifier; + stack[c].val.num = num; + stack[c].type = STACK_LONG; return; } @@ -105,72 +135,40 @@ static void push_string(const char *identifier, char *str) { // XXX: ideally, this would be const char. need to check if that works with all // called functions. +// TODO move to a common util static char *get_string(const char *identifier) { for (int c = 0; c < 10; c++) { if (stack[c].identifier == NULL) break; if (strcmp(identifier, stack[c].identifier) == 0) - return stack[c].str; + return stack[c].val.str; } return NULL; } +// TODO move to a common util +static long get_long(const char *identifier) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier == NULL) + break; + if (strcmp(identifier, stack[c].identifier) == 0) + return stack[c].val.num; + } + + return 0; +} + +// TODO move to a common util static void clear_stack(void) { for (int c = 0; c < 10; c++) { - if (stack[c].str != NULL) - free(stack[c].str); + if (stack[c].type == STACK_STR && stack[c].val.str != NULL) + free(stack[c].val.str); stack[c].identifier = NULL; - stack[c].str = NULL; + stack[c].val.str = NULL; + stack[c].val.num = 0; } } -// TODO: remove this if it turns out we don’t need it for testing. -#if 0 -/******************************************************************************* - * A dynamically growing linked list which holds the criteria for the current - * command. - ******************************************************************************/ - -typedef struct criterion { - char *type; - char *value; - - TAILQ_ENTRY(criterion) criteria; -} criterion; - -static TAILQ_HEAD(criteria_head, criterion) criteria = - TAILQ_HEAD_INITIALIZER(criteria); - -/* - * Stores the given type/value in the list of criteria. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void push_criterion(void *unused_criteria, const char *type, - const char *value) { - struct criterion *criterion = smalloc(sizeof(struct criterion)); - criterion->type = sstrdup(type); - criterion->value = sstrdup(value); - TAILQ_INSERT_TAIL(&criteria, criterion, criteria); -} - -/* - * Clears the criteria linked list. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void clear_criteria(void *unused_criteria) { - struct criterion *criterion; - while (!TAILQ_EMPTY(&criteria)) { - criterion = TAILQ_FIRST(&criteria); - free(criterion->type); - free(criterion->value); - TAILQ_REMOVE(&criteria, criterion, criteria); - free(criterion); - } -} -#endif - /******************************************************************************* * The parser itself. ******************************************************************************/ @@ -316,6 +314,29 @@ CommandResult *parse_command(const char *input, yajl_gen gen) { continue; } + if (strcmp(token->name, "number") == 0) { + /* Handle numbers. We only accept decimal numbers for now. */ + char *end = NULL; + errno = 0; + long int num = strtol(walk, &end, 10); + if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || + (errno != 0 && num == 0)) + continue; + + /* No valid numbers found */ + if (end == walk) + continue; + + if (token->identifier != NULL) + push_long(token->identifier, num); + + /* Set walk to the first non-number character */ + walk = end; + next_state(token); + token_handled = true; + break; + } + if (strcmp(token->name, "string") == 0 || strcmp(token->name, "word") == 0) { char *str = parse_string(&walk, (token->name[0] != 's')); diff --git a/src/config_parser.c b/src/config_parser.c index ea00412d..705a3e24 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -122,7 +122,7 @@ static void push_string(const char *identifier, const char *str) { /* When we arrive here, the stack is full. This should not happen and * means there’s either a bug in this parser or the specification * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + fprintf(stderr, "BUG: config_parser stack full. This means either a bug " "in the code, or a new command which contains more than " "10 identified tokens.\n"); exit(1); @@ -142,7 +142,7 @@ static void push_long(const char *identifier, long num) { /* When we arrive here, the stack is full. This should not happen and * means there’s either a bug in this parser or the specification * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + fprintf(stderr, "BUG: config_parser stack full. This means either a bug " "in the code, or a new command which contains more than " "10 identified tokens.\n"); exit(1); @@ -178,53 +178,6 @@ static void clear_stack(void) { } } -// TODO: remove this if it turns out we don’t need it for testing. -#if 0 -/******************************************************************************* - * A dynamically growing linked list which holds the criteria for the current - * command. - ******************************************************************************/ - -typedef struct criterion { - char *type; - char *value; - - TAILQ_ENTRY(criterion) criteria; -} criterion; - -static TAILQ_HEAD(criteria_head, criterion) criteria = - TAILQ_HEAD_INITIALIZER(criteria); - -/* - * Stores the given type/value in the list of criteria. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void push_criterion(void *unused_criteria, const char *type, - const char *value) { - struct criterion *criterion = smalloc(sizeof(struct criterion)); - criterion->type = sstrdup(type); - criterion->value = sstrdup(value); - TAILQ_INSERT_TAIL(&criteria, criterion, criteria); -} - -/* - * Clears the criteria linked list. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void clear_criteria(void *unused_criteria) { - struct criterion *criterion; - while (!TAILQ_EMPTY(&criteria)) { - criterion = TAILQ_FIRST(&criteria); - free(criterion->type); - free(criterion->value); - TAILQ_REMOVE(&criteria, criterion, criteria); - free(criterion); - } -} -#endif - /******************************************************************************* * The parser itself. ******************************************************************************/ From a271666fa7c94b78e06db292457b373fac948732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 16:32:54 +0200 Subject: [PATCH 010/123] Migrate the resize command to use typed numbers. --- include/commands.h | 4 ++-- parser-specs/commands.spec | 16 ++++++++-------- src/commands.c | 33 ++++++++++++++------------------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/include/commands.h b/include/commands.h index 80b0efb0..de6c499d 100644 --- a/include/commands.h +++ b/include/commands.h @@ -64,13 +64,13 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which); * Implementation of 'resize set [px] [px]'. * */ -void cmd_size(I3_CMD, char *cwidth, char *cheight); +void cmd_resize_set(I3_CMD, long cwidth, long cheight); /** * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt); +void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt); /** * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 5e2bfd8f..c7510914 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -225,10 +225,10 @@ state RESIZE_DIRECTION: -> RESIZE_PX state RESIZE_PX: - resize_px = word + resize_px = number -> RESIZE_TILING end - -> call cmd_resize($way, $direction, "10", "10") + -> call cmd_resize($way, $direction, 10, 10) state RESIZE_TILING: 'px' @@ -236,29 +236,29 @@ state RESIZE_TILING: 'or' -> RESIZE_TILING_OR end - -> call cmd_resize($way, $direction, $resize_px, "10") + -> call cmd_resize($way, $direction, &resize_px, 10) state RESIZE_TILING_OR: - resize_ppt = word + resize_ppt = number -> RESIZE_TILING_FINAL state RESIZE_TILING_FINAL: 'ppt', end - -> call cmd_resize($way, $direction, $resize_px, $resize_ppt) + -> call cmd_resize($way, $direction, &resize_px, &resize_ppt) state RESIZE_SET: - width = word + width = number -> RESIZE_WIDTH state RESIZE_WIDTH: 'px' -> - height = word + height = number -> RESIZE_HEIGHT state RESIZE_HEIGHT: 'px', end - -> call cmd_size($width, $height) + -> call cmd_resize_set(&width, &height) # rename workspace to # rename workspace to diff --git a/src/commands.c b/src/commands.c index a696ad34..ec657a64 100644 --- a/src/commands.c +++ b/src/commands.c @@ -782,15 +782,11 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) { - /* resize [ px] [or ppt] */ - DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt); - // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking - int px = atoi(resize_px); - int ppt = atoi(resize_ppt); +void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt) { + DLOG("resizing in way %s, direction %s, px %ld or ppt %ld\n", way, direction, resize_px, resize_ppt); if (strcmp(way, "shrink") == 0) { - px *= -1; - ppt *= -1; + resize_px *= -1; + resize_ppt *= -1; } HANDLE_EMPTY_MATCH; @@ -805,14 +801,16 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz Con *floating_con; if ((floating_con = con_inside_floating(current->con))) { - cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px); + cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, resize_px); } else { if (strcmp(direction, "width") == 0 || strcmp(direction, "height") == 0) { - if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt)) + if (!cmd_resize_tiling_width_height(current_match, cmd_output, + current->con, way, direction, resize_ppt)) return; } else { - if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt)) + if (!cmd_resize_tiling_direction(current_match, cmd_output, + current->con, way, direction, resize_ppt)) return; } } @@ -827,13 +825,10 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz * Implementation of 'resize set [px] [px]'. * */ -void cmd_size(I3_CMD, char *cwidth, char *cheight) { - DLOG("resizing to %sx%s px\n", cwidth, cheight); - // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking - int x = atoi(cwidth); - int y = atoi(cheight); - if (x <= 0 || y <= 0) { - ELOG("Resize failed: dimensions cannot be negative (was %sx%s)\n", cwidth, cheight); +void cmd_resize_set(I3_CMD, long cwidth, long cheight) { + DLOG("resizing to %ldx%ld px\n", cwidth, cheight); + if (cwidth <= 0 || cheight <= 0) { + ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight); return; } @@ -843,7 +838,7 @@ void cmd_size(I3_CMD, char *cwidth, char *cheight) { TAILQ_FOREACH(current, &owindows, owindows) { Con *floating_con; if ((floating_con = con_inside_floating(current->con))) { - floating_resize(floating_con, x, y); + floating_resize(floating_con, cwidth, cheight); } else { ELOG("Resize failed: %p not a floating container\n", current->con); } From 9978050d91f93f116427df3335afd49fd0637488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 16:59:36 +0200 Subject: [PATCH 011/123] Migrate the move command to use typed numbers. --- include/commands.h | 4 ++-- parser-specs/commands.spec | 14 +++++++------- src/commands.c | 25 ++++++++++--------------- testcases/t/187-commands-parser.t | 2 +- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/include/commands.h b/include/commands.h index de6c499d..ec94d371 100644 --- a/include/commands.h +++ b/include/commands.h @@ -214,7 +214,7 @@ void cmd_sticky(I3_CMD, char *action); * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, char *move_px); +void cmd_move_direction(I3_CMD, char *direction, long move_px); /** * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. @@ -262,7 +262,7 @@ void cmd_focus_output(I3_CMD, char *name); * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, char *x, char *y); +void cmd_move_window_to_position(I3_CMD, char *method, long x, long y); /** * Implementation of 'move [window|container] [to] [absolute] position center diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index c7510914..b3b5e338 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -320,16 +320,16 @@ state MOVE: -> MOVE_TO_ABSOLUTE_POSITION state MOVE_DIRECTION: - pixels = word + pixels = number -> MOVE_DIRECTION_PX end - -> call cmd_move_direction($direction, "10") + -> call cmd_move_direction($direction, 10) state MOVE_DIRECTION_PX: 'px' - -> call cmd_move_direction($direction, $pixels) + -> call cmd_move_direction($direction, &pixels) end - -> call cmd_move_direction($direction, $pixels) + -> call cmd_move_direction($direction, &pixels) state MOVE_WORKSPACE: 'to ' @@ -370,18 +370,18 @@ state MOVE_TO_POSITION: -> call cmd_move_window_to_center($method) 'mouse', 'cursor', 'pointer' -> call cmd_move_window_to_mouse() - coord_x = word + coord_x = number -> MOVE_TO_POSITION_X state MOVE_TO_POSITION_X: 'px' -> - coord_y = word + coord_y = number -> MOVE_TO_POSITION_Y state MOVE_TO_POSITION_Y: 'px', end - -> call cmd_move_window_to_position($method, $coord_x, $coord_y) + -> call cmd_move_window_to_position($method, &coord_x, &coord_y) # mode state MODE: diff --git a/src/commands.c b/src/commands.c index ec657a64..be975f4c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1565,28 +1565,25 @@ void cmd_sticky(I3_CMD, char *action) { * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, char *move_px) { - // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking - int px = atoi(move_px); - +void cmd_move_direction(I3_CMD, char *direction, long move_px) { owindow *current; HANDLE_EMPTY_MATCH; Con *initially_focused = focused; TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("moving in direction %s, px %s\n", direction, move_px); + DLOG("moving in direction %s, px %ld\n", direction, move_px); if (con_is_floating(current->con)) { - DLOG("floating move with %d pixels\n", px); + DLOG("floating move with %ld pixels\n", move_px); Rect newrect = current->con->parent->rect; if (strcmp(direction, "left") == 0) { - newrect.x -= px; + newrect.x -= move_px; } else if (strcmp(direction, "right") == 0) { - newrect.x += px; + newrect.x += move_px; } else if (strcmp(direction, "up") == 0) { - newrect.y -= px; + newrect.y -= move_px; } else if (strcmp(direction, "down") == 0) { - newrect.y += px; + newrect.y += move_px; } floating_reposition(current->con->parent, newrect); } else { @@ -1788,9 +1785,7 @@ void cmd_focus_output(I3_CMD, char *name) { * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { - int x = atoi(cx); - int y = atoi(cy); +void cmd_move_window_to_position(I3_CMD, char *method, long x, long y) { bool has_error = false; owindow *current; @@ -1812,7 +1807,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { current->con->parent->rect.x = x; current->con->parent->rect.y = y; - DLOG("moving to absolute position %d %d\n", x, y); + DLOG("moving to absolute position %ld %ld\n", x, y); floating_maybe_reassign_ws(current->con->parent); cmd_output->needs_tree_render = true; } @@ -1820,7 +1815,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { if (strcmp(method, "position") == 0) { Rect newrect = current->con->parent->rect; - DLOG("moving to position %d %d\n", x, y); + DLOG("moving to position %ld %ld\n", x, y); newrect.x = x; newrect.y = y; diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 4f555b29..a56d668e 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -216,7 +216,7 @@ is(parser_calls('workspace "foo\\\\\\"bar"'), ################################################################################ is(parser_calls("resize shrink width 10 px or"), - "ERROR: Expected one of these tokens: \n" . + "ERROR: Expected one of these tokens: \n" . "ERROR: Your command: resize shrink width 10 px or\n" . "ERROR: ", "error for resize command with incomplete 'or'-construction ok"); From 27535398f5635dbc9a0a0732b2d150ab091e80ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 23:42:58 +0200 Subject: [PATCH 012/123] Turn "char *" into "const char *" for all command parser functions. --- include/commands.h | 74 ++++++++++++++++++------------------ include/startup.h | 2 +- include/workspace.h | 2 +- src/commands.c | 87 ++++++++++++++++++++++--------------------- src/commands_parser.c | 4 +- src/startup.c | 2 +- src/workspace.c | 2 +- 7 files changed, 86 insertions(+), 87 deletions(-) diff --git a/include/commands.h b/include/commands.h index ec94d371..e0bb2f92 100644 --- a/include/commands.h +++ b/include/commands.h @@ -33,14 +33,14 @@ void cmd_criteria_match_windows(I3_CMD); * specification. * */ -void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue); +void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue); /** * Implementation of 'move [window|container] [to] workspace * next|prev|next_on_output|prev_on_output'. * */ -void cmd_move_con_to_workspace(I3_CMD, char *which); +void cmd_move_con_to_workspace(I3_CMD, const char *which); /** * Implementation of 'move [window|container] [to] workspace back_and_forth'. @@ -52,13 +52,13 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD); * Implementation of 'move [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, char *name); +void cmd_move_con_to_workspace_name(I3_CMD, const char *name); /** * Implementation of 'move [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, char *which); +void cmd_move_con_to_workspace_number(I3_CMD, const char *which); /** * Implementation of 'resize set [px] [px]'. @@ -70,37 +70,37 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight); * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt); +void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, long resize_ppt); /** * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, char *border_style_str, char *border_width); +void cmd_border(I3_CMD, const char *border_style_str, const char *border_width); /** * Implementation of 'nop '. * */ -void cmd_nop(I3_CMD, char *comment); +void cmd_nop(I3_CMD, const char *comment); /** * Implementation of 'append_layout '. * */ -void cmd_append_layout(I3_CMD, char *path); +void cmd_append_layout(I3_CMD, const char *path); /** * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. * */ -void cmd_workspace(I3_CMD, char *which); +void cmd_workspace(I3_CMD, const char *which); /** * Implementation of 'workspace number ' * */ -void cmd_workspace_number(I3_CMD, char *which); +void cmd_workspace_number(I3_CMD, const char *which); /** * Implementation of 'workspace back_and_forth'. @@ -112,85 +112,85 @@ void cmd_workspace_back_and_forth(I3_CMD); * Implementation of 'workspace ' * */ -void cmd_workspace_name(I3_CMD, char *name); +void cmd_workspace_name(I3_CMD, const char *name); /** * Implementation of 'mark [--toggle] ' * */ -void cmd_mark(I3_CMD, char *mark, char *toggle); +void cmd_mark(I3_CMD, const char *mark, const char *toggle); /** * Implementation of 'unmark [mark]' * */ -void cmd_unmark(I3_CMD, char *mark); +void cmd_unmark(I3_CMD, const char *mark); /** * Implementation of 'mode '. * */ -void cmd_mode(I3_CMD, char *mode); +void cmd_mode(I3_CMD, const char *mode); /** * Implementation of 'move [window|container] [to] output '. * */ -void cmd_move_con_to_output(I3_CMD, char *name); +void cmd_move_con_to_output(I3_CMD, const char *name); /** * Implementation of 'move [window|container] [to] mark '. * */ -void cmd_move_con_to_mark(I3_CMD, char *mark); +void cmd_move_con_to_mark(I3_CMD, const char *mark); /** * Implementation of 'floating enable|disable|toggle' * */ -void cmd_floating(I3_CMD, char *floating_mode); +void cmd_floating(I3_CMD, const char *floating_mode); /** * Implementation of 'move workspace to [output] '. * */ -void cmd_move_workspace_to_output(I3_CMD, char *name); +void cmd_move_workspace_to_output(I3_CMD, const char *name); /** * Implementation of 'split v|h|vertical|horizontal'. * */ -void cmd_split(I3_CMD, char *direction); +void cmd_split(I3_CMD, const char *direction); /** * Implementation of 'kill [window|client]'. * */ -void cmd_kill(I3_CMD, char *kill_mode_str); +void cmd_kill(I3_CMD, const char *kill_mode_str); /** * Implementation of 'exec [--no-startup-id] '. * */ -void cmd_exec(I3_CMD, char *nosn, char *command); +void cmd_exec(I3_CMD, const char *nosn, const char *command); /** * Implementation of 'focus left|right|up|down'. * */ -void cmd_focus_direction(I3_CMD, char *direction); +void cmd_focus_direction(I3_CMD, const char *direction); /** * Implementation of 'focus tiling|floating|mode_toggle'. * */ -void cmd_focus_window_mode(I3_CMD, char *window_mode); +void cmd_focus_window_mode(I3_CMD, const char *window_mode); /** * Implementation of 'focus parent|child'. * */ -void cmd_focus_level(I3_CMD, char *level); +void cmd_focus_level(I3_CMD, const char *level); /** * Implementation of 'focus'. @@ -202,31 +202,31 @@ void cmd_focus(I3_CMD); * Implementation of 'fullscreen [enable|disable|toggle] [global]'. * */ -void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode); +void cmd_fullscreen(I3_CMD, const char *action, const char *fullscreen_mode); /** * Implementation of 'sticky enable|disable|toggle'. * */ -void cmd_sticky(I3_CMD, char *action); +void cmd_sticky(I3_CMD, const char *action); /** * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, long move_px); +void cmd_move_direction(I3_CMD, const char *direction, long move_px); /** * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. * */ -void cmd_layout(I3_CMD, char *layout_str); +void cmd_layout(I3_CMD, const char *layout_str); /** * Implementation of 'layout toggle [all|split]'. * */ -void cmd_layout_toggle(I3_CMD, char *toggle_mode); +void cmd_layout_toggle(I3_CMD, const char *toggle_mode); /** * Implementation of 'exit'. @@ -256,19 +256,19 @@ void cmd_open(I3_CMD); * Implementation of 'focus output '. * */ -void cmd_focus_output(I3_CMD, char *name); +void cmd_focus_output(I3_CMD, const char *name); /** * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, long x, long y); +void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y); /** * Implementation of 'move [window|container] [to] [absolute] position center * */ -void cmd_move_window_to_center(I3_CMD, char *method); +void cmd_move_window_to_center(I3_CMD, const char *method); /** * Implementation of 'move [window|container] [to] position mouse' @@ -292,28 +292,28 @@ void cmd_scratchpad_show(I3_CMD); * Implementation of 'title_format ' * */ -void cmd_title_format(I3_CMD, char *format); +void cmd_title_format(I3_CMD, const char *format); /** * Implementation of 'rename workspace to ' * */ -void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name); +void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name); /** * Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) []' * */ -void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id); +void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id); /* * Implementation of 'shmlog |toggle|on|off' * */ -void cmd_shmlog(I3_CMD, char *argument); +void cmd_shmlog(I3_CMD, const char *argument); /* * Implementation of 'debuglog toggle|on|off' * */ -void cmd_debuglog(I3_CMD, char *argument); +void cmd_debuglog(I3_CMD, const char *argument); diff --git a/include/startup.h b/include/startup.h index 7d5d2a39..9729cdc2 100644 --- a/include/startup.h +++ b/include/startup.h @@ -48,7 +48,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata); * Renames workspaces that are mentioned in the startup sequences. * */ -void startup_sequence_rename_workspace(char *old_name, char *new_name); +void startup_sequence_rename_workspace(const char *old_name, const char *new_name); /** * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. diff --git a/include/workspace.h b/include/workspace.h index 21ab18d6..1bee64e0 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -194,4 +194,4 @@ Con *workspace_encapsulate(Con *ws); * This returns true if and only if moving the workspace was successful. * */ -bool workspace_move_to_output(Con *ws, char *output); +bool workspace_move_to_output(Con *ws, const char *output); diff --git a/src/commands.c b/src/commands.c index be975f4c..95784c77 100644 --- a/src/commands.c +++ b/src/commands.c @@ -83,7 +83,7 @@ static Output *get_output_of_con(Con *con) { * and return true, signaling that no further workspace switching should occur in the calling function. * */ -static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, char *name) { +static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, const char *name) { Con *ws = con_get_workspace(focused); /* If we switched to a different workspace, do nothing */ @@ -315,7 +315,7 @@ void cmd_criteria_match_windows(I3_CMD) { * specification. * */ -void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { +void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) { DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); if (strcmp(ctype, "class") == 0) { @@ -424,7 +424,7 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) { * next|prev|next_on_output|prev_on_output|current'. * */ -void cmd_move_con_to_workspace(I3_CMD, char *which) { +void cmd_move_con_to_workspace(I3_CMD, const char *which) { owindow *current; DLOG("which=%s\n", which); @@ -500,7 +500,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { * Implementation of 'move [window|container] [to] workspace '. * */ -void cmd_move_con_to_workspace_name(I3_CMD, char *name) { +void cmd_move_con_to_workspace_name(I3_CMD, const char *name) { if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name); ysuccess(false); @@ -544,7 +544,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { * Implementation of 'move [window|container] [to] workspace number '. * */ -void cmd_move_con_to_workspace_number(I3_CMD, char *which) { +void cmd_move_con_to_workspace_number(I3_CMD, const char *which) { owindow *current; /* We have nothing to move: @@ -591,7 +591,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { ysuccess(true); } -static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) { +static void cmd_resize_floating(I3_CMD, const char *way, const char *direction, Con *floating_con, int px) { LOG("floating resize\n"); Rect old_rect = floating_con->rect; Con *focused_con = con_descend_focused(floating_con); @@ -643,7 +643,7 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin floating_con->scratchpad_state = SCRATCHPAD_CHANGED; } -static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { +static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int ppt) { LOG("tiling resize\n"); Con *second = NULL; Con *first = current; @@ -696,7 +696,7 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *d return true; } -static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char *direction, int ppt) { +static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) { LOG("width/height resize\n"); /* get the appropriate current container (skip stacked/tabbed cons) */ while (current->parent->layout == L_STACKED || @@ -782,7 +782,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char * Implementation of 'resize grow|shrink [ px] [or ppt]'. * */ -void cmd_resize(I3_CMD, char *way, char *direction, long resize_px, long resize_ppt) { +void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, long resize_ppt) { DLOG("resizing in way %s, direction %s, px %ld or ppt %ld\n", way, direction, resize_px, resize_ppt); if (strcmp(way, "shrink") == 0) { resize_px *= -1; @@ -853,7 +853,7 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight) { * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, char *border_style_str, char *border_width) { +void cmd_border(I3_CMD, const char *border_style_str, const char *border_width) { DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width); owindow *current; @@ -906,7 +906,7 @@ void cmd_border(I3_CMD, char *border_style_str, char *border_width) { * Implementation of 'nop '. * */ -void cmd_nop(I3_CMD, char *comment) { +void cmd_nop(I3_CMD, const char *comment) { LOG("-------------------------------------------------\n"); LOG(" NOP: %s\n", comment); LOG("-------------------------------------------------\n"); @@ -916,7 +916,8 @@ void cmd_nop(I3_CMD, char *comment) { * Implementation of 'append_layout '. * */ -void cmd_append_layout(I3_CMD, char *path) { +void cmd_append_layout(I3_CMD, const char *cpath) { + char *path = sstrdup(cpath); LOG("Appending layout \"%s\"\n", path); /* Make sure we allow paths like '~/.i3/layout.json' */ @@ -977,7 +978,7 @@ void cmd_append_layout(I3_CMD, char *path) { * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. * */ -void cmd_workspace(I3_CMD, char *which) { +void cmd_workspace(I3_CMD, const char *which) { Con *ws; DLOG("which=%s\n", which); @@ -1013,7 +1014,7 @@ void cmd_workspace(I3_CMD, char *which) { * Implementation of 'workspace number ' * */ -void cmd_workspace_number(I3_CMD, char *which) { +void cmd_workspace_number(I3_CMD, const char *which) { Con *output, *workspace = NULL; if (con_get_fullscreen_con(croot, CF_GLOBAL)) { @@ -1072,7 +1073,7 @@ void cmd_workspace_back_and_forth(I3_CMD) { * Implementation of 'workspace ' * */ -void cmd_workspace_name(I3_CMD, char *name) { +void cmd_workspace_name(I3_CMD, const char *name) { if (strncasecmp(name, "__", strlen("__")) == 0) { LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name); ysuccess(false); @@ -1099,7 +1100,7 @@ void cmd_workspace_name(I3_CMD, char *name) { * Implementation of 'mark [--toggle] ' * */ -void cmd_mark(I3_CMD, char *mark, char *toggle) { +void cmd_mark(I3_CMD, const char *mark, const char *toggle) { HANDLE_EMPTY_MATCH; owindow *current = TAILQ_FIRST(&owindows); @@ -1130,7 +1131,7 @@ void cmd_mark(I3_CMD, char *mark, char *toggle) { * Implementation of 'unmark [mark]' * */ -void cmd_unmark(I3_CMD, char *mark) { +void cmd_unmark(I3_CMD, const char *mark) { con_unmark(mark); cmd_output->needs_tree_render = true; @@ -1142,7 +1143,7 @@ void cmd_unmark(I3_CMD, char *mark) { * Implementation of 'mode '. * */ -void cmd_mode(I3_CMD, char *mode) { +void cmd_mode(I3_CMD, const char *mode) { DLOG("mode=%s\n", mode); switch_mode(mode); @@ -1154,7 +1155,7 @@ void cmd_mode(I3_CMD, char *mode) { * Implementation of 'move [window|container] [to] output '. * */ -void cmd_move_con_to_output(I3_CMD, char *name) { +void cmd_move_con_to_output(I3_CMD, const char *name) { DLOG("Should move window to output \"%s\".\n", name); HANDLE_EMPTY_MATCH; @@ -1192,7 +1193,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) { * Implementation of 'move [container|window] [to] mark '. * */ -void cmd_move_con_to_mark(I3_CMD, char *mark) { +void cmd_move_con_to_mark(I3_CMD, const char *mark) { DLOG("moving window to mark \"%s\"\n", mark); HANDLE_EMPTY_MATCH; @@ -1212,7 +1213,7 @@ void cmd_move_con_to_mark(I3_CMD, char *mark) { * Implementation of 'floating enable|disable|toggle' * */ -void cmd_floating(I3_CMD, char *floating_mode) { +void cmd_floating(I3_CMD, const char *floating_mode) { owindow *current; DLOG("floating_mode=%s\n", floating_mode); @@ -1243,7 +1244,7 @@ void cmd_floating(I3_CMD, char *floating_mode) { * Implementation of 'move workspace to [output] '. * */ -void cmd_move_workspace_to_output(I3_CMD, char *name) { +void cmd_move_workspace_to_output(I3_CMD, const char *name) { DLOG("should move workspace to output %s\n", name); HANDLE_EMPTY_MATCH; @@ -1268,7 +1269,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { * Implementation of 'split v|h|vertical|horizontal'. * */ -void cmd_split(I3_CMD, char *direction) { +void cmd_split(I3_CMD, const char *direction) { owindow *current; /* TODO: use matches */ LOG("splitting in direction %c\n", direction[0]); @@ -1290,7 +1291,7 @@ void cmd_split(I3_CMD, char *direction) { * Implementation of 'kill [window|client]'. * */ -void cmd_kill(I3_CMD, char *kill_mode_str) { +void cmd_kill(I3_CMD, const char *kill_mode_str) { if (kill_mode_str == NULL) kill_mode_str = "window"; owindow *current; @@ -1327,7 +1328,7 @@ void cmd_kill(I3_CMD, char *kill_mode_str) { * Implementation of 'exec [--no-startup-id] '. * */ -void cmd_exec(I3_CMD, char *nosn, char *command) { +void cmd_exec(I3_CMD, const char *nosn, const char *command) { bool no_startup_id = (nosn != NULL); DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id); @@ -1341,7 +1342,7 @@ void cmd_exec(I3_CMD, char *nosn, char *command) { * Implementation of 'focus left|right|up|down'. * */ -void cmd_focus_direction(I3_CMD, char *direction) { +void cmd_focus_direction(I3_CMD, const char *direction) { DLOG("direction = *%s*\n", direction); if (strcmp(direction, "left") == 0) @@ -1367,7 +1368,7 @@ void cmd_focus_direction(I3_CMD, char *direction) { * Implementation of 'focus tiling|floating|mode_toggle'. * */ -void cmd_focus_window_mode(I3_CMD, char *window_mode) { +void cmd_focus_window_mode(I3_CMD, const char *window_mode) { DLOG("window_mode = %s\n", window_mode); Con *ws = con_get_workspace(focused); @@ -1398,7 +1399,7 @@ void cmd_focus_window_mode(I3_CMD, char *window_mode) { * Implementation of 'focus parent|child'. * */ -void cmd_focus_level(I3_CMD, char *level) { +void cmd_focus_level(I3_CMD, const char *level) { DLOG("level = %s\n", level); bool success = false; @@ -1502,7 +1503,7 @@ void cmd_focus(I3_CMD) { * 'fullscreen disable' * */ -void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) { +void cmd_fullscreen(I3_CMD, const char *action, const char *fullscreen_mode) { fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT; DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode); owindow *current; @@ -1529,7 +1530,7 @@ void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) { * Implementation of 'sticky enable|disable|toggle'. * */ -void cmd_sticky(I3_CMD, char *action) { +void cmd_sticky(I3_CMD, const char *action) { DLOG("%s sticky on window\n", action); HANDLE_EMPTY_MATCH; @@ -1565,7 +1566,7 @@ void cmd_sticky(I3_CMD, char *action) { * Implementation of 'move [ [px]]'. * */ -void cmd_move_direction(I3_CMD, char *direction, long move_px) { +void cmd_move_direction(I3_CMD, const char *direction, long move_px) { owindow *current; HANDLE_EMPTY_MATCH; @@ -1604,7 +1605,7 @@ void cmd_move_direction(I3_CMD, char *direction, long move_px) { * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'. * */ -void cmd_layout(I3_CMD, char *layout_str) { +void cmd_layout(I3_CMD, const char *layout_str) { if (strcmp(layout_str, "stacking") == 0) layout_str = "stacked"; owindow *current; @@ -1646,7 +1647,7 @@ void cmd_layout(I3_CMD, char *layout_str) { * Implementation of 'layout toggle [all|split]'. * */ -void cmd_layout_toggle(I3_CMD, char *toggle_mode) { +void cmd_layout_toggle(I3_CMD, const char *toggle_mode) { owindow *current; if (toggle_mode == NULL) @@ -1743,7 +1744,7 @@ void cmd_open(I3_CMD) { * Implementation of 'focus output '. * */ -void cmd_focus_output(I3_CMD, char *name) { +void cmd_focus_output(I3_CMD, const char *name) { owindow *current; DLOG("name = %s\n", name); @@ -1785,7 +1786,7 @@ void cmd_focus_output(I3_CMD, char *name) { * Implementation of 'move [window|container] [to] [absolute] position [px] [px] * */ -void cmd_move_window_to_position(I3_CMD, char *method, long x, long y) { +void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) { bool has_error = false; owindow *current; @@ -1832,7 +1833,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, long x, long y) { * Implementation of 'move [window|container] [to] [absolute] position center * */ -void cmd_move_window_to_center(I3_CMD, char *method) { +void cmd_move_window_to_center(I3_CMD, const char *method) { if (!con_is_floating(focused)) { ELOG("Cannot change position. The window/container is not floating\n"); yerror("Cannot change position. The window/container is not floating."); @@ -1928,7 +1929,7 @@ void cmd_scratchpad_show(I3_CMD) { * Implementation of 'title_format ' * */ -void cmd_title_format(I3_CMD, char *format) { +void cmd_title_format(I3_CMD, const char *format) { DLOG("setting title_format to \"%s\"\n", format); HANDLE_EMPTY_MATCH; @@ -1965,7 +1966,7 @@ void cmd_title_format(I3_CMD, char *format) { * Implementation of 'rename workspace [] to ' * */ -void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { +void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { if (strncasecmp(new_name, "__", strlen("__")) == 0) { LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.\n", new_name); ysuccess(false); @@ -2050,7 +2051,7 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { * Implementation of 'bar mode dock|hide|invisible|toggle []' * */ -bool cmd_bar_mode(char *bar_mode, char *bar_id) { +bool cmd_bar_mode(const char *bar_mode, const char *bar_id) { int mode = M_DOCK; bool toggle = false; if (strcmp(bar_mode, "dock") == 0) @@ -2095,7 +2096,7 @@ bool cmd_bar_mode(char *bar_mode, char *bar_id) { * Implementation of 'bar hidden_state hide|show|toggle []' * */ -bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { +bool cmd_bar_hidden_state(const char *bar_hidden_state, const char *bar_id) { int hidden_state = S_SHOW; bool toggle = false; if (strcmp(bar_hidden_state, "hide") == 0) @@ -2138,7 +2139,7 @@ bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { * Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) []' * */ -void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) { +void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id) { bool ret; if (strcmp(bar_type, "mode") == 0) ret = cmd_bar_mode(bar_value, bar_id); @@ -2160,7 +2161,7 @@ void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) { * Implementation of 'shmlog |toggle|on|off' * */ -void cmd_shmlog(I3_CMD, char *argument) { +void cmd_shmlog(I3_CMD, const char *argument) { if (!strcmp(argument, "toggle")) /* Toggle shm log, if size is not 0. If it is 0, set it to default. */ shmlog_size = shmlog_size ? -shmlog_size : default_shmlog_size; @@ -2191,7 +2192,7 @@ void cmd_shmlog(I3_CMD, char *argument) { * Implementation of 'debuglog toggle|on|off' * */ -void cmd_debuglog(I3_CMD, char *argument) { +void cmd_debuglog(I3_CMD, const char *argument) { bool logging = get_debug_logging(); if (!strcmp(argument, "toggle")) { LOG("%s debug logging\n", logging ? "Disabling" : "Enabling"); diff --git a/src/commands_parser.c b/src/commands_parser.c index d311fdd1..cdd35e45 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -133,10 +133,8 @@ static void push_long(const char *identifier, long num) { exit(1); } -// XXX: ideally, this would be const char. need to check if that works with all -// called functions. // TODO move to a common util -static char *get_string(const char *identifier) { +static const char *get_string(const char *identifier) { for (int c = 0; c < 10; c++) { if (stack[c].identifier == NULL) break; diff --git a/src/startup.c b/src/startup.c index 400d3192..b7950c20 100644 --- a/src/startup.c +++ b/src/startup.c @@ -261,7 +261,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { * Renames workspaces that are mentioned in the startup sequences. * */ -void startup_sequence_rename_workspace(char *old_name, char *new_name) { +void startup_sequence_rename_workspace(const char *old_name, const char *new_name) { struct Startup_Sequence *current; TAILQ_FOREACH(current, &startup_sequences, sequences) { if (strcmp(current->workspace, old_name) != 0) diff --git a/src/workspace.c b/src/workspace.c index d3a97453..e7a09c70 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -918,7 +918,7 @@ Con *workspace_encapsulate(Con *ws) { * Move the given workspace to the specified output. * This returns true if and only if moving the workspace was successful. */ -bool workspace_move_to_output(Con *ws, char *name) { +bool workspace_move_to_output(Con *ws, const char *name) { LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name); Con *current_output_con = con_get_output(ws); From 5648221d067ebf1590ce91a20fa0a0e4cad62a38 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 19 Oct 2015 18:52:47 +0200 Subject: [PATCH 013/123] Fix memleak in translate_keysyms --- src/bindings.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bindings.c b/src/bindings.c index b9c7d914..84998af1 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -396,6 +396,7 @@ void translate_keysyms(void) { } xkb_state_unref(dummy_state); + xkb_state_unref(dummy_state_no_shift); } /* From 724cfebe5e948804676d280dfee839b197bc8eef Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 19 Oct 2015 19:11:53 +0200 Subject: [PATCH 014/123] fix a memory leak in handle_get_bar_config --- src/ipc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 1a8d28ed..c884cc8e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -894,8 +894,8 @@ IPC_HANDLER(get_bar_config) { /* To get a properly terminated buffer, we copy * message_size bytes out of the buffer */ - char *bar_id = scalloc(message_size + 1, 1); - strncpy(bar_id, (const char *)message, message_size); + char *bar_id = NULL; + sasprintf(&bar_id, "%.*s", message_size, message); LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id); Barconfig *current, *config = NULL; TAILQ_FOREACH(current, &barconfigs, configs) { @@ -905,6 +905,7 @@ IPC_HANDLER(get_bar_config) { config = current; break; } + free(bar_id); if (!config) { /* If we did not find a config for the given ID, the reply will contain From 2a22b5d561e8fb72b0d23ba08bbbae12fe8d85bd Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Wed, 21 Oct 2015 13:08:21 +0200 Subject: [PATCH 015/123] Quote __focused__ to prevent parsing by asciidoc Text between two "__" is rendered as italics by asciidoc. This affects the display of the new __focused__ special value for criteria. --- docs/userguide | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/userguide b/docs/userguide index 2a2ce869..87fd8bbf 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1642,15 +1642,15 @@ The criteria which are currently implemented are: class:: Compares the window class (the second part of WM_CLASS). Use the - special value +__focused__+ to match all windows having the same window + special value +\_\_focused__+ to match all windows having the same window class as the currently focused window. instance:: Compares the window instance (the first part of WM_CLASS). Use the - special value +__focused__+ to match all windows having the same window + special value +\_\_focused__+ to match all windows having the same window instance as the currently focused window. window_role:: Compares the window role (WM_WINDOW_ROLE). Use the special value - +__focused__+ to match all windows having the same window role as the + +\_\_focused__+ to match all windows having the same window role as the currently focused window. window_type:: Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are @@ -1659,8 +1659,8 @@ window_type:: id:: Compares the X11 window ID, which you can get via +xwininfo+ for example. title:: - Compares the X11 window title (_NET_WM_NAME or WM_NAME as fallback). - Use the special value +__focused__+ to match all windows having the + Compares the X11 window title (\_NET_WM_NAME or WM_NAME as fallback). + Use the special value +\_\_focused__+ to match all windows having the same window title as the currently focused window. urgent:: Compares the urgent state of the window. Can be "latest" or "oldest". @@ -1668,7 +1668,7 @@ urgent:: (The following aliases are also available: newest, last, recent, first) workspace:: Compares the workspace name of the workspace the window belongs to. Use - the special value +__focused__+ to match all windows in the currently + the special value +\_\_focused__+ to match all windows in the currently focused workspace. con_mark:: Compares the mark set for this container, see <>. From 2e5cfdeea0b4e4e03757b8fe7e44e00288d5d5f8 Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Wed, 21 Oct 2015 13:35:52 +0200 Subject: [PATCH 016/123] Improve placement of explicit IDs for headings In some cases the IDs of section titles was placed after the section title. With that in the rendered HTML the ID was placed on the paragraph and not on the heading. This led to heading not being shown when the corresponding link was clicked. --- docs/userguide | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/docs/userguide b/docs/userguide index 87fd8bbf..4c6eceaf 100644 --- a/docs/userguide +++ b/docs/userguide @@ -205,9 +205,8 @@ like this: image::tree-layout2.png["layout2",float="right"] image::tree-shot4.png["shot4",title="Two terminals on standard workspace"] -=== Orientation and Split Containers - [[OrientationSplit]] +=== Orientation and Split Containers It is only natural to use so-called +Split Containers+ in order to build a layout when using a tree as data structure. In i3, every +Container+ has an @@ -309,7 +308,6 @@ a # and can only be used at the beginning of a line: ------------------- [[fonts]] - === Fonts i3 has support for both X core fonts and FreeType fonts (through Pango) to @@ -342,7 +340,6 @@ font pango:Terminus 11px -------------------------------------------------------------- [[keybindings]] - === Keyboard bindings A keyboard binding makes i3 execute a command (see below) upon pressing a @@ -407,7 +404,6 @@ corresponding group. For backwards compatibility, the group “Mode_switch” is alias for Group2. [[mousebindings]] - === Mouse bindings A mouse binding makes i3 execute a command upon pressing a specific mouse @@ -445,7 +441,6 @@ bindsym button8 move right -------------------------------- [[binding_modes]] - === Binding modes You can have multiple sets of bindings by using different binding modes. When @@ -498,7 +493,6 @@ mode "$mode_launcher" { ------------------------------------------------------------------------ [[floating_modifier]] - === The floating modifier To move floating windows with your mouse, you can either grab their titlebar @@ -626,9 +620,8 @@ hide_edge_borders none|vertical|horizontal|both hide_edge_borders vertical ---------------------- -=== Arbitrary commands for specific windows (for_window) - [[for_window]] +=== Arbitrary commands for specific windows (for_window) With the +for_window+ command, you can let i3 execute any command when it encounters a specific window. This can be used to set windows to floating or to @@ -655,9 +648,8 @@ for_window [title="x200: ~/work"] floating enable The valid criteria are the same as those for commands, see <>. -=== Don't focus window upon opening - [[no_focus]] +=== Don't focus window upon opening When a new window appears, it will be focused. The +no_focus+ directive allows preventing this from happening and can be used in combination with <>. @@ -681,7 +673,6 @@ no_focus [window_role="pop-up"] ------------------------------- [[variables]] - === Variables As you learned in the section about keyboard bindings, you will have @@ -707,9 +698,8 @@ absolutely no plans to change this. If you need a more dynamic configuration you should create a little script which generates a configuration file and run it before starting i3 (for example in your +~/.xsession+ file). -=== Automatically putting clients on specific workspaces - [[assign_workspace]] +=== Automatically putting clients on specific workspaces To automatically make a specific window show up on a specific workspace, you can use an *assignment*. You can match windows by using any criteria, @@ -814,7 +804,6 @@ exec --no-startup-id urxvt The flag --no-startup-id is explained in <>. [[workspace_screen]] - === Automatically putting workspaces on specific screens If you assign clients to workspaces, it might be handy to put the @@ -1073,9 +1062,8 @@ force_display_urgency_hint ms force_display_urgency_hint 500 ms --------------------------------- -=== Focus on window activation - [[focus_on_window_activation]] +=== Focus on window activation If a window is activated, e.g., via +google-chrome www.google.com+, it may request to take focus. Since this may not preferable, different reactions can be configured. @@ -1100,6 +1088,7 @@ focus:: none:: The window will neither be focused, nor be marked urgent. +[[show_marks]] === Drawing marks on window decoration If activated, marks on windows are drawn in their window decoration. However, @@ -1119,7 +1108,6 @@ show_marks yes -------------- [[line_continuation]] - === Line continuation Config files support line continuation, meaning when you end a line in a @@ -1681,7 +1669,6 @@ actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for information on how to use them. [[exec]] - === Executing applications (exec) What good is a window manager if you can’t actually start any applications? @@ -1779,7 +1766,6 @@ bindsym $mod+t floating toggle -------------- [[_focusing_moving_containers]] - === Focusing containers To change focus, you can use the +focus+ command. The following options are @@ -2011,9 +1997,8 @@ bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: ' See <> for how to move a container/workspace to a different RandR output. -=== Moving containers/workspaces to RandR outputs - [[move_to_outputs]] +=== Moving containers/workspaces to RandR outputs To move a container to another RandR output (addressed by names like +LVDS1+ or +VGA1+) or to a RandR output identified by a specific direction (like +left+, @@ -2056,7 +2041,6 @@ for_window [instance="tabme"] move window to mark target -------------------------------------------------------- [[resizingconfig]] - === Resizing containers/windows If you want to resize containers/windows using your keyboard, you can use the @@ -2108,9 +2092,8 @@ with criteria for that. bindsym $mod+a [class="urxvt" title="VIM"] focus ------------------------------------------------ -=== VIM-like marks (mark/goto) - [[vim_like_marks]] +=== VIM-like marks (mark/goto) This feature is like the jump feature: It allows you to directly jump to a specific window (this means switching to the appropriate workspace and setting @@ -2129,7 +2112,7 @@ The additional +--toggle+ option will remove the mark if the window already has this mark, add it if the window has none or replace the current mark if it has another mark. -Refer to +show_marks+ if you don't want marks to be shown in the window decoration. +Refer to <> if you don't want marks to be shown in the window decoration. *Syntax*: ------------------------------ @@ -2161,7 +2144,6 @@ seperate bindings for a specific set of labels and then only use those labels. /////////////////////////////////////////////////////////////////// [[pango_markup]] - === Window title format By default, i3 will simply print the X11 window title. Using +title_format+, @@ -2227,7 +2209,6 @@ bindsym $mod+u border none ---------------------------------------------- [[shmlog]] - === Enabling shared memory logging As described in http://i3wm.org/docs/debugging.html, i3 can log to a shared @@ -2376,7 +2357,6 @@ bindsym $mod+Shift+b bar mode invisible bar-1 ------------------------------------------------ [[multi_monitor]] - == Multiple monitors As you can see in the goal list on the website, i3 was specifically developed @@ -2506,6 +2486,7 @@ position the window either at the top or at the bottom of the screen, depending on which hint the application sets. With i3bar, you can configure its position, see <>. +[[presentations]] === Giving presentations (multi-monitor) When giving a presentation, you typically want the audience to see what you see @@ -2514,7 +2495,6 @@ simple). For more complex presentations, you might want to have some notes which only you can see on your screen, while the audience can only see the slides. -[[presentations]] ==== Case 1: everybody gets the same output This is the simple case. You connect your computer to the video projector, turn on both (computer and video projector) and configure your X server to From 76db8b73ae3427e58e9335d6b25548c23de6400e Mon Sep 17 00:00:00 2001 From: Adaephon-GH Date: Wed, 21 Oct 2015 13:58:45 +0200 Subject: [PATCH 017/123] Make rendering of key bindings more consistent - Render key names and key bindings verbatim if they could be used like that in the configuration (no special format for "colloquial" names: Alt, Windows, ...) - Use only lower case letters for key bindings --- docs/userguide | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/userguide b/docs/userguide index 4c6eceaf..eef7d758 100644 --- a/docs/userguide +++ b/docs/userguide @@ -33,15 +33,15 @@ above, just decline i3-config-wizard’s offer and base your config on == Using i3 Throughout this guide, the keyword +$mod+ will be used to refer to the -configured modifier. This is the Alt key (Mod1) by default, with the Windows -key (Mod4) being a popular alternative. +configured modifier. This is the Alt key (+Mod1+) by default, with the Windows +key (+Mod4+) being a popular alternative. === Opening terminals and moving around One very basic operation is opening a new terminal. By default, the keybinding -for this is $mod+Enter, that is Alt+Enter in the default configuration. By -pressing $mod+Enter, a new terminal will be opened. It will fill the whole -space available on your screen. +for this is +$mod+Enter+, that is Alt+Enter (+Mod1+Enter+) in the default +configuration. By pressing +$mod+Enter+, a new terminal will be opened. It +will fill the whole space available on your screen. image:single_terminal.png[Single terminal] @@ -55,9 +55,9 @@ image:two_terminals.png[Two terminals] To move the focus between the two terminals, you can use the direction keys which you may know from the editor +vi+. However, in i3, your homerow is used for these keys (in +vi+, the keys are shifted to the left by one for -compatibility with most keyboard layouts). Therefore, +$mod+J+ is left, +$mod+K+ -is down, +$mod+L+ is up and `$mod+;` is right. So, to switch between the -terminals, use +$mod+K+ or +$mod+L+. Of course, you can also use the arrow keys. +compatibility with most keyboard layouts). Therefore, +$mod+j+ is left, +$mod+k+ +is down, +$mod+l+ is up and `$mod+;` is right. So, to switch between the +terminals, use +$mod+k+ or +$mod+l+. Of course, you can also use the arrow keys. At the moment, your workspace is split (it contains two terminals) in a specific direction (horizontal by default). Every window can be split @@ -114,7 +114,7 @@ create a keybinding for starting the application directly. See the section === Closing windows If an application does not provide a mechanism for closing (most applications -provide a menu, the escape key or a shortcut like +Control+W+ to close), you +provide a menu, the escape key or a shortcut like +Control+w+ to close), you can press +$mod+Shift+q+ to kill a window. For applications which support the WM_DELETE protocol, this will correctly close the application (saving any modifications or doing other cleanup). If the application doesn’t support @@ -290,7 +290,7 @@ with a text editor. On first start (and on all following starts, unless you have a configuration file), i3 will offer you to create a configuration file. You can tell the -wizard to use either Alt (Mod1) or Windows (Mod4) as modifier in the config +wizard to use either Alt (+Mod1+) or Windows (+Mod4+) as modifier in the config file. Also, the created config file will use the key symbols of your current keyboard layout. To start the wizard, use the command +i3-config-wizard+. Please note that you must not have +~/.i3/config+, otherwise the wizard will From 7c75d61a392c8b10668dd5390bf56b4cc8400bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 19 Oct 2015 21:17:35 +0200 Subject: [PATCH 018/123] Activate root output if RandR request fails. fixes #2011 --- src/randr.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/randr.c b/src/randr.c index e4522c4d..81a33e62 100644 --- a/src/randr.c +++ b/src/randr.c @@ -604,7 +604,6 @@ void randr_query_outputs(void) { Output *output, *other, *first; xcb_randr_get_output_primary_cookie_t pcookie; xcb_randr_get_screen_resources_current_cookie_t rcookie; - resources_reply *res; /* timestamp of the configuration so that we get consistent replies to all * requests (if the configuration changes between our different calls) */ @@ -621,28 +620,31 @@ void randr_query_outputs(void) { ELOG("Could not get RandR primary output\n"); else DLOG("primary output is %08x\n", primary->output); - if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) - return; - cts = res->config_timestamp; + resources_reply *res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); + if (res == NULL) { + ELOG("Could not query screen resources.\n"); + } else { + cts = res->config_timestamp; - int len = xcb_randr_get_screen_resources_current_outputs_length(res); - randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + int len = xcb_randr_get_screen_resources_current_outputs_length(res); + randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); - /* Request information for each output */ - xcb_randr_get_output_info_cookie_t ocookie[len]; - for (int i = 0; i < len; i++) - ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); - /* Loop through all outputs available for this X11 screen */ - for (int i = 0; i < len; i++) { - xcb_randr_get_output_info_reply_t *output; + /* Loop through all outputs available for this X11 screen */ + for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; - if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) - continue; + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; - handle_output(conn, randr_outputs[i], output, cts, res); - free(output); + handle_output(conn, randr_outputs[i], output, cts, res); + free(output); + } } /* If there's no randr output, enable the output covering the root window. */ From 0aa8d05b5476caca4047f1a9c59b0d55ccd64782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 25 Oct 2015 14:25:55 +0100 Subject: [PATCH 019/123] Fixed logging statement. Assignments don't necessarily represent workspace assignments, but could also be used, e.g., for no_focus. Hence, there's no point in logging dest.workspace for all assignments. --- src/assignments.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assignments.c b/src/assignments.c index babe890e..9de50e12 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -76,7 +76,7 @@ Assignment *assignment_for(i3Window *window, int type) { if ((type != A_ANY && (assignment->type & type) == 0) || !match_matches_window(&(assignment->match), window)) continue; - DLOG("got a matching assignment (to %s)\n", assignment->dest.workspace); + DLOG("got a matching assignment\n"); return assignment; } From 1f953719c9af6d9a7f84097730d8160caab3b8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 25 Oct 2015 14:27:08 +0100 Subject: [PATCH 020/123] Mark assignment as run before executing it. We need to store the information that an assignment was run for a window before actually executing the command. Otherwise, if the command causes a change that causes assignments to be run again, the window might be matched again, causing an infinite loop and hence i3 to freeze or crash. --- src/assignments.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/assignments.c b/src/assignments.c index 9de50e12..6c563357 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -40,6 +40,13 @@ void run_assignments(i3Window *window) { if (skip) continue; + /* Store that we ran this assignment to not execute it again. We have + * to do this before running the actual command to prevent infinite + * loops. */ + window->nr_assignments++; + window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments); + window->ran_assignments[window->nr_assignments - 1] = current; + DLOG("matching assignment, would do:\n"); if (current->type == A_COMMAND) { DLOG("execute command %s\n", current->dest.command); @@ -53,11 +60,6 @@ void run_assignments(i3Window *window) { command_result_free(result); } - - /* Store that we ran this assignment to not execute it again */ - window->nr_assignments++; - window->ran_assignments = srealloc(window->ran_assignments, sizeof(Assignment *) * window->nr_assignments); - window->ran_assignments[window->nr_assignments - 1] = current; } /* If any of the commands required re-rendering, we will do that now. */ From f02413b4cfba84d2d732d9534eb1434c5ba1cc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Mon, 26 Oct 2015 18:16:21 +0100 Subject: [PATCH 021/123] Fix crash when trying to split and float a dock container. Since splitting a docking container was allowed and successful, the check to prevent floating it fails to work. This causes a crash because the workspace of the container cannot be determined as the dockarea is higher up in the tree than the workspace it belongs to. This patch extends to sanity check to nested dock containers when trying to float a container and also disallows manually splitting a docked container or changing its layout. fixes #2034 --- include/con.h | 6 ++++++ src/commands.c | 34 +++++++++++++++++++--------------- src/con.c | 14 ++++++++++++++ src/floating.c | 2 +- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/include/con.h b/include/con.h index 16ea6cfa..2e853016 100644 --- a/include/con.h +++ b/include/con.h @@ -112,6 +112,12 @@ bool con_is_internal(Con *con); */ bool con_is_floating(Con *con); +/** + * Returns true if the container is a docked container. + * + */ +bool con_is_docked(Con *con); + /** * Checks if the given container is either floating or inside some floating * container. It returns the FLOATING_CON container. diff --git a/src/commands.c b/src/commands.c index 95784c77..56b95061 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1270,16 +1270,18 @@ void cmd_move_workspace_to_output(I3_CMD, const char *name) { * */ void cmd_split(I3_CMD, const char *direction) { + HANDLE_EMPTY_MATCH; + owindow *current; - /* TODO: use matches */ LOG("splitting in direction %c\n", direction[0]); - if (match_is_empty(current_match)) - tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ)); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); + TAILQ_FOREACH(current, &owindows, owindows) { + if (con_is_docked(current->con)) { + ELOG("Cannot split a docked container, skipping.\n"); + continue; } + + DLOG("matching: %p / %s\n", current->con, current->con->name); + tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); } cmd_output->needs_tree_render = true; @@ -1606,9 +1608,10 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) { * */ void cmd_layout(I3_CMD, const char *layout_str) { + HANDLE_EMPTY_MATCH; + if (strcmp(layout_str, "stacking") == 0) layout_str = "stacked"; - owindow *current; layout_t layout; /* default is a special case which will be handled in con_set_layout(). */ if (strcmp(layout_str, "default") == 0) @@ -1628,14 +1631,15 @@ void cmd_layout(I3_CMD, const char *layout_str) { DLOG("changing layout to %s (%d)\n", layout_str, layout); - /* check if the match is empty, not if the result is empty */ - if (match_is_empty(current_match)) - con_set_layout(focused, layout); - else { - TAILQ_FOREACH(current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - con_set_layout(current->con, layout); + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + if (con_is_docked(current->con)) { + ELOG("cannot change layout of a docked container, skipping it.\n"); + continue; } + + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_set_layout(current->con, layout); } cmd_output->needs_tree_render = true; diff --git a/src/con.c b/src/con.c index 5b0cc6c8..4233a8a8 100644 --- a/src/con.c +++ b/src/con.c @@ -447,6 +447,20 @@ bool con_is_floating(Con *con) { return (con->floating >= FLOATING_AUTO_ON); } +/* + * Returns true if the container is a docked container. + * + */ +bool con_is_docked(Con *con) { + if (con->parent == NULL) + return false; + + if (con->parent->type == CT_DOCKAREA) + return true; + + return con_is_docked(con->parent); +} + /* * Checks if the given container is either floating or inside some floating * container. It returns the FLOATING_CON container. diff --git a/src/floating.c b/src/floating.c index d7a33067..77bc9e17 100644 --- a/src/floating.c +++ b/src/floating.c @@ -108,7 +108,7 @@ void floating_check_size(Con *floating_con) { void floating_enable(Con *con, bool automatic) { bool set_focus = (con == focused); - if (con->parent && con->parent->type == CT_DOCKAREA) { + if (con_is_docked(con)) { LOG("Container is a dock window, not enabling floating mode.\n"); return; } From a22dec02f8c049ead4619d70a222298b480a31e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Sun, 27 Sep 2015 09:42:26 +0200 Subject: [PATCH 022/123] Refactor parsing of matches to avoid code duplication. --- include/match.h | 6 +++ src/commands.c | 102 +------------------------------------ src/config_directives.c | 104 +------------------------------------- src/match.c | 109 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 204 deletions(-) diff --git a/include/match.h b/include/match.h index dbd9bb79..64a4f22b 100644 --- a/include/match.h +++ b/include/match.h @@ -45,3 +45,9 @@ bool match_matches_window(Match *match, i3Window *window); * */ void match_free(Match *match); + +/** + * Interprets a ctype=cvalue pair and adds it to the given match specification. + * + */ +void match_parse_property(Match *match, const char *ctype, const char *cvalue); diff --git a/src/commands.c b/src/commands.c index 56b95061..78dc2159 100644 --- a/src/commands.c +++ b/src/commands.c @@ -316,107 +316,7 @@ void cmd_criteria_match_windows(I3_CMD) { * */ void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) { - DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); - - if (strcmp(ctype, "class") == 0) { - current_match->class = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "instance") == 0) { - current_match->instance = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "window_role") == 0) { - current_match->window_role = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "con_id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse con id \"%s\"\n", cvalue); - } else { - current_match->con_id = (Con *)parsed; - DLOG("id as int = %p\n", current_match->con_id); - } - return; - } - - if (strcmp(ctype, "id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse window id \"%s\"\n", cvalue); - } else { - current_match->id = parsed; - DLOG("window id as int = %d\n", current_match->id); - } - return; - } - - if (strcmp(ctype, "window_type") == 0) { - if (strcasecmp(cvalue, "normal") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; - else if (strcasecmp(cvalue, "dialog") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; - else if (strcasecmp(cvalue, "utility") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; - else if (strcasecmp(cvalue, "toolbar") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; - else if (strcasecmp(cvalue, "splash") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; - else if (strcasecmp(cvalue, "menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU; - else if (strcasecmp(cvalue, "dropdown_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; - else if (strcasecmp(cvalue, "popup_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; - else if (strcasecmp(cvalue, "tooltip") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; - else - ELOG("unknown window_type value \"%s\"\n", cvalue); - - return; - } - - if (strcmp(ctype, "con_mark") == 0) { - current_match->mark = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "title") == 0) { - current_match->title = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "urgent") == 0) { - if (strcasecmp(cvalue, "latest") == 0 || - strcasecmp(cvalue, "newest") == 0 || - strcasecmp(cvalue, "recent") == 0 || - strcasecmp(cvalue, "last") == 0) { - current_match->urgent = U_LATEST; - } else if (strcasecmp(cvalue, "oldest") == 0 || - strcasecmp(cvalue, "first") == 0) { - current_match->urgent = U_OLDEST; - } - return; - } - - if (strcmp(ctype, "workspace") == 0) { - current_match->workspace = regex_new(cvalue); - return; - } - - ELOG("Unknown criterion: %s\n", ctype); + match_parse_property(current_match, ctype, cvalue); } /* diff --git a/src/config_directives.c b/src/config_directives.c index d772387d..cd0432fe 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -42,111 +42,9 @@ CFGFUN(criteria_pop_state) { * */ CFGFUN(criteria_add, const char *ctype, const char *cvalue) { - DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); - - if (strcmp(ctype, "class") == 0) { - current_match->class = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "instance") == 0) { - current_match->instance = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "window_role") == 0) { - current_match->window_role = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "con_id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse con id \"%s\"\n", cvalue); - } else { - current_match->con_id = (Con *)parsed; - DLOG("id as int = %p\n", current_match->con_id); - } - return; - } - - if (strcmp(ctype, "id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse window id \"%s\"\n", cvalue); - } else { - current_match->id = parsed; - DLOG("window id as int = %d\n", current_match->id); - } - return; - } - - if (strcmp(ctype, "window_type") == 0) { - if (strcasecmp(cvalue, "normal") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; - else if (strcasecmp(cvalue, "dialog") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; - else if (strcasecmp(cvalue, "utility") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; - else if (strcasecmp(cvalue, "toolbar") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; - else if (strcasecmp(cvalue, "splash") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; - else if (strcasecmp(cvalue, "menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU; - else if (strcasecmp(cvalue, "dropdown_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; - else if (strcasecmp(cvalue, "popup_menu") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; - else if (strcasecmp(cvalue, "tooltip") == 0) - current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; - else - ELOG("unknown window_type value \"%s\"\n", cvalue); - - return; - } - - if (strcmp(ctype, "con_mark") == 0) { - current_match->mark = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "title") == 0) { - current_match->title = regex_new(cvalue); - return; - } - - if (strcmp(ctype, "urgent") == 0) { - if (strcasecmp(cvalue, "latest") == 0 || - strcasecmp(cvalue, "newest") == 0 || - strcasecmp(cvalue, "recent") == 0 || - strcasecmp(cvalue, "last") == 0) { - current_match->urgent = U_LATEST; - } else if (strcasecmp(cvalue, "oldest") == 0 || - strcasecmp(cvalue, "first") == 0) { - current_match->urgent = U_OLDEST; - } - return; - } - - if (strcmp(ctype, "workspace") == 0) { - current_match->workspace = regex_new(cvalue); - return; - } - - ELOG("Unknown criterion: %s\n", ctype); + match_parse_property(current_match, ctype, cvalue); } -/* TODO: refactor the above criteria code into a single file (with src/commands.c). */ - /******************************************************************************* * Utility functions ******************************************************************************/ diff --git a/src/match.c b/src/match.c index 20af38f6..67054dae 100644 --- a/src/match.c +++ b/src/match.c @@ -254,3 +254,112 @@ void match_free(Match *match) { FREE(match->mark); FREE(match->window_role); } + +/* + * Interprets a ctype=cvalue pair and adds it to the given match specification. + * + */ +void match_parse_property(Match *match, const char *ctype, const char *cvalue) { + assert(match != NULL); + DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); + + if (strcmp(ctype, "class") == 0) { + match->class = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "instance") == 0) { + match->instance = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "window_role") == 0) { + match->window_role = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "con_id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse con id \"%s\"\n", cvalue); + } else { + match->con_id = (Con *)parsed; + DLOG("id as int = %p\n", match->con_id); + } + return; + } + + if (strcmp(ctype, "id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse window id \"%s\"\n", cvalue); + } else { + match->id = parsed; + DLOG("window id as int = %d\n", match->id); + } + return; + } + + if (strcmp(ctype, "window_type") == 0) { + if (strcasecmp(cvalue, "normal") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL; + else if (strcasecmp(cvalue, "dialog") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG; + else if (strcasecmp(cvalue, "utility") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY; + else if (strcasecmp(cvalue, "toolbar") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR; + else if (strcasecmp(cvalue, "splash") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH; + else if (strcasecmp(cvalue, "menu") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_MENU; + else if (strcasecmp(cvalue, "dropdown_menu") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU; + else if (strcasecmp(cvalue, "popup_menu") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU; + else if (strcasecmp(cvalue, "tooltip") == 0) + match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP; + else + ELOG("unknown window_type value \"%s\"\n", cvalue); + + return; + } + + if (strcmp(ctype, "con_mark") == 0) { + match->mark = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "title") == 0) { + match->title = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "urgent") == 0) { + if (strcasecmp(cvalue, "latest") == 0 || + strcasecmp(cvalue, "newest") == 0 || + strcasecmp(cvalue, "recent") == 0 || + strcasecmp(cvalue, "last") == 0) { + match->urgent = U_LATEST; + } else if (strcasecmp(cvalue, "oldest") == 0 || + strcasecmp(cvalue, "first") == 0) { + match->urgent = U_OLDEST; + } + return; + } + + if (strcmp(ctype, "workspace") == 0) { + match->workspace = regex_new(cvalue); + return; + } + + ELOG("Unknown criterion: %s\n", ctype); +} From 4779e59c509c72bc7431fe2fab47c16342ef8d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Wed, 28 Oct 2015 14:39:23 +0100 Subject: [PATCH 023/123] Fix multiple memory leaks with regular expressions. --- src/config_directives.c | 1 + src/manage.c | 1 + src/match.c | 16 +++++++--------- src/regex.c | 1 + 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/config_directives.c b/src/config_directives.c index cd0432fe..fdfc1c7d 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -29,6 +29,7 @@ CFGFUN(criteria_init, int _state) { criteria_next_state = _state; DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state); + match_free(current_match); match_init(current_match); } diff --git a/src/manage.c b/src/manage.c index 5cfe490e..0dec2844 100644 --- a/src/manage.c +++ b/src/manage.c @@ -294,6 +294,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (match != NULL && match->insert_where != M_BELOW) { DLOG("Removing match %p from container %p\n", match, nc); TAILQ_REMOVE(&(nc->swallow_head), match, matches); + match_free(match); } } diff --git a/src/match.c b/src/match.c index 67054dae..2cdf4f45 100644 --- a/src/match.c +++ b/src/match.c @@ -238,21 +238,13 @@ bool match_matches_window(Match *match, i3Window *window) { * */ void match_free(Match *match) { - /* First step: free the regex fields / patterns */ regex_free(match->title); regex_free(match->application); regex_free(match->class); regex_free(match->instance); regex_free(match->mark); regex_free(match->window_role); - - /* Second step: free the regex helper struct itself */ - FREE(match->title); - FREE(match->application); - FREE(match->class); - FREE(match->instance); - FREE(match->mark); - FREE(match->window_role); + regex_free(match->workspace); } /* @@ -264,16 +256,19 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); if (strcmp(ctype, "class") == 0) { + regex_free(match->class); match->class = regex_new(cvalue); return; } if (strcmp(ctype, "instance") == 0) { + regex_free(match->instance); match->instance = regex_new(cvalue); return; } if (strcmp(ctype, "window_role") == 0) { + regex_free(match->window_role); match->window_role = regex_new(cvalue); return; } @@ -334,11 +329,13 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "con_mark") == 0) { + regex_free(match->mark); match->mark = regex_new(cvalue); return; } if (strcmp(ctype, "title") == 0) { + regex_free(match->title); match->title = regex_new(cvalue); return; } @@ -357,6 +354,7 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "workspace") == 0) { + regex_free(match->workspace); match->workspace = regex_new(cvalue); return; } diff --git a/src/regex.c b/src/regex.c index 913519be..24846981 100644 --- a/src/regex.c +++ b/src/regex.c @@ -64,6 +64,7 @@ void regex_free(struct regex *regex) { FREE(regex->pattern); FREE(regex->regex); FREE(regex->extra); + FREE(regex); } /* From 82806f3857ef7eb7752c5e54711200ebba265cdf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 28 Oct 2015 21:42:37 +0100 Subject: [PATCH 024/123] Bugfix: correctly compare modifier mask when identifying keybindings fixes #2002 --- src/bindings.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 84998af1..a3ea7cf6 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -165,15 +165,23 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas } TAILQ_FOREACH(bind, bindings, bindings) { + bool state_matches; + if (bind->event_state_mask == 0) { + /* Verify no modifiers are pressed. A bitwise AND would lead to + * false positives, see issue #2002. */ + state_matches = (state_filtered == 0); + } else { + state_matches = ((state_filtered & bind->event_state_mask) == bind->event_state_mask); + } + DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n", - bind->event_state_mask, state_filtered, - ((state_filtered & bind->event_state_mask) == bind->event_state_mask) ? "yes" : "no"); + bind->event_state_mask, state_filtered, (state_matches ? "yes" : "no")); /* First compare the state_filtered (unless this is a * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease * event) */ if (bind->input_type != input_type) continue; - if ((state_filtered & bind->event_state_mask) != bind->event_state_mask && + if (!state_matches && (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || !is_release)) continue; From 1f9f44b694aed696d2d14a13bf665f4c9263c0fe Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Oct 2015 00:12:38 +0200 Subject: [PATCH 025/123] travis: install clang-format-3.5 from llvm repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ubuntu utopic disappeared from archive.ubuntu.com, it’s EOL. --- .travis.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f90e0ebd..dd1fb156 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,20 @@ language: c compiler: - gcc - clang +addons: + apt: + sources: + - llvm-toolchain-precise-3.5 + # ubuntu-toolchain-r-test contains libstdc++6 >= 4.8 which libllvm3.5 needs. + - ubuntu-toolchain-r-test + packages: + - clang-format-3.5 + - libllvm3.5 before_install: # The travis VMs run on Ubuntu 12.04 which is very old and a huge pain to get # into a state where we can build a recent version of i3 :(. - "echo 'deb http://archive.ubuntu.com/ubuntu/ trusty main universe' | sudo tee /etc/apt/sources.list.d/trusty.list" - "echo 'APT::Default-Release \"precise\";' | sudo tee /etc/apt/apt.conf.d/default-release" - - "echo 'deb http://archive.ubuntu.com/ubuntu/ utopic main universe' | sudo tee /etc/apt/sources.list.d/utopic.list" - "echo 'Package: libc6' > /tmp/pin" - "echo 'Pin: release n=trusty' >> /tmp/pin" @@ -33,8 +41,6 @@ before_install: - sudo apt-get update - sudo apt-get install -t trusty libc6 libc6-dev - sudo apt-get install --no-install-recommends devscripts equivs xdotool - - sudo apt-get install -t utopic clang-format-3.5 - - clang-format-3.5 --version install: - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug. From 7a057de9692d18d9eb2bb640aa9da1274004b082 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2016 16:17:28 +0100 Subject: [PATCH 026/123] Update debian/changelog --- debian/changelog | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 8857ad01..bfbc8382 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,11 @@ -i3-wm (4.11.1-1) unstable; urgency=medium +i3-wm (4.12-1) unstable; urgency=medium * New upstream release. + * Move to debhelper 9 + * Bump Standards-Version to 3.9.7 (no changes necessary) + * debian/watch: verify signature, use https - -- Michael Stapelberg Wed, 30 Sep 2015 09:03:26 +0200 + -- Michael Stapelberg Sun, 06 Mar 2016 14:20:38 +0100 i3-wm (4.11-1) unstable; urgency=medium From 0e29101ae5f3753626ba447b83a9b8bd490de37e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 6 Mar 2016 16:59:33 +0100 Subject: [PATCH 027/123] fix i3 4.12 merge issue in src/commands.c (Thanks Airblader) --- src/commands.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/commands.c b/src/commands.c index c9580c28..0faf2775 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1168,9 +1168,6 @@ void cmd_split(I3_CMD, const char *direction) { } else { tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); } - - DLOG("matching: %p / %s\n", current->con, current->con->name); - tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ)); } cmd_output->needs_tree_render = true; From 7785e7be7dae5c8de4fb300b125e0e0f751392f9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 8 Nov 2016 19:54:15 +0100 Subject: [PATCH 028/123] Update debian/changelog --- debian/changelog | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/debian/changelog b/debian/changelog index 80e2d5ec..bb08c7fd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,11 +1,8 @@ -i3-wm (4.12.1-1) unstable; urgency=medium +i3-wm (4.13-1) unstable; urgency=medium * New upstream release. - * Move to debhelper 9 - * Bump Standards-Version to 3.9.7 (no changes necessary) - * debian/watch: verify signature, use https - -- Michael Stapelberg Fri, 01 Apr 2016 16:34:35 +0200 + -- Michael Stapelberg Tue, 08 Nov 2016 19:02:16 +0100 i3-wm (4.12-2) unstable; urgency=medium From e1f6a3e3d3310904b2c74373eb50b28b3a3394af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 4 Sep 2017 07:53:39 +0200 Subject: [PATCH 029/123] Update debian/changelog --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 9333c665..dae55d7e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,8 @@ -i3-wm (4.13.1-1) unstable; urgency=medium +i3-wm (4.14-1) unstable; urgency=medium * New upstream release. - -- Michael Stapelberg Tue, 08 Nov 2016 21:31:13 +0100 + -- Michael Stapelberg Mon, 04 Sep 2017 07:53:16 +0200 i3-wm (4.13-1) unstable; urgency=medium From e8dbf0171de8c7399dcdfc3c42610e5f3bf418d1 Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Wed, 23 Aug 2017 15:48:58 +0200 Subject: [PATCH 030/123] Avoid use of uninitialized in init_dpi_end If conn == NULL or display == NULL, init_dpi() jumps to init_dpi_end before (declaring and) initializing resource. In init_dpi_end, there is a free(resource) call conditionally on resource != NULL, so this may lead to a bogus free. Found by clang -Wsometimes-uninitialized. --- libi3/dpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi3/dpi.c b/libi3/dpi.c index ce85cacc..93a3c6f6 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -24,6 +24,7 @@ static long init_dpi_fallback(void) { */ void init_dpi(void) { xcb_xrm_database_t *database = NULL; + char *resource = NULL; if (conn == NULL) { goto init_dpi_end; @@ -35,7 +36,6 @@ void init_dpi(void) { goto init_dpi_end; } - char *resource; xcb_xrm_resource_get_string(database, "Xft.dpi", NULL, &resource); if (resource == NULL) { DLOG("Resource Xft.dpi not specified, skipping.\n"); From 09ee12d8e5e1cd219493fde3393dbca10c0cc23f Mon Sep 17 00:00:00 2001 From: hwangcc23 Date: Thu, 31 Aug 2017 22:48:33 +0800 Subject: [PATCH 031/123] Properly initialize sigaction struct The code in handle_signal() wasn't clearing the struct sigaction before passing it to sigaction(). This meant that we would block a random set of signals while executing the default handler, or jump to the uninitialized __sa_sigaction__ (instead of sa_handler). Initialize properly as we do in setup_signal_handler(). --- src/sighandler.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sighandler.c b/src/sighandler.c index b1e7d166..1a161d2a 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -305,6 +305,8 @@ void handle_signal(int sig, siginfo_t *info, void *data) { struct sigaction action; action.sa_handler = SIG_DFL; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); sigaction(sig, &action, NULL); raised_signal = sig; From dedfda1e016aabb4f7416def99baba5bb8c3ef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 5 Sep 2017 09:01:53 +0200 Subject: [PATCH 032/123] Invert condition to log debug message in correct situation (#2896) --- src/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 393d7018..bbe7d265 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1854,7 +1854,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) { return; } - if (match == TAILQ_LAST(&owindows, owindows_head)) { + if (match != TAILQ_LAST(&owindows, owindows_head)) { DLOG("More than one container matched the swap command, only using the first one."); } From 369c9ed50f2b9fc2dc904db07907cd5f95f0a5ad Mon Sep 17 00:00:00 2001 From: Orestis Date: Wed, 6 Sep 2017 08:34:14 +0300 Subject: [PATCH 033/123] Check if con_id exists in cmd_swap (#2898) Also adds some testcases for swap using con_id. Fixes #2895 --- include/con.h | 7 +++++++ src/commands.c | 2 +- src/con.c | 16 ++++++++++++++++ testcases/t/265-swap.t | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/include/con.h b/include/con.h index 0fa660a1..1c7bb932 100644 --- a/include/con.h +++ b/include/con.h @@ -152,6 +152,13 @@ bool con_has_parent(Con *con, Con *parent); */ Con *con_by_window_id(xcb_window_t window); +/** + * Returns the container with the given container ID or NULL if no such + * container exists. + * + */ +Con *con_by_con_id(long target); + /** * Returns the container with the given frame ID or NULL if no such container * exists. diff --git a/src/commands.c b/src/commands.c index bbe7d265..faee3916 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1841,7 +1841,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) { return; } - con = (Con *)target; + con = con_by_con_id(target); } else if (strcmp(mode, "mark") == 0) { con = con_by_mark(arg); } else { diff --git a/src/con.c b/src/con.c index cf923ec8..b520c110 100644 --- a/src/con.c +++ b/src/con.c @@ -557,6 +557,22 @@ Con *con_by_window_id(xcb_window_t window) { return NULL; } +/* + * Returns the container with the given container ID or NULL if no such + * container exists. + * + */ +Con *con_by_con_id(long target) { + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con == (Con *)target) { + return con; + } + } + + return NULL; +} + /* * Returns the container with the given frame ID or NULL if no such container * exists. diff --git a/testcases/t/265-swap.t b/testcases/t/265-swap.t index f86bba71..e3c8e97c 100644 --- a/testcases/t/265-swap.t +++ b/testcases/t/265-swap.t @@ -32,6 +32,38 @@ my ($nodes, $expected_focus, $A, $B, $F); my ($result); my @urgent; +############################################################################### +# Invalid con_id should not crash i3 +# See issue #2895. +############################################################################### + +$pid = launch_with_config($config); +$ws = fresh_workspace; + +open_window; +cmd "swap container with con_id 1"; + +does_i3_live; +exit_gracefully($pid); + +############################################################################### +# Swap 2 windows in different workspaces using con_id +############################################################################### + +$pid = launch_with_config($config); + +$ws = fresh_workspace; +open_window; +$A = get_focused($ws); + +$ws = fresh_workspace; +open_window; + +cmd "swap container with con_id $A"; +is(get_focused($ws), $A, 'A is now focused'); + +exit_gracefully($pid); + ############################################################################### # Swap two containers next to each other. # Focus should stay on B because both windows are on the focused workspace. From f26b00cb67957b7f8809fff0523f6ac9ae930465 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 7 Sep 2017 04:19:57 +0300 Subject: [PATCH 034/123] Improve 267-regress-mark-restart.t Another window with a mark is needed for issue #2900. --- testcases/t/267-regress-mark-restart.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testcases/t/267-regress-mark-restart.t b/testcases/t/267-regress-mark-restart.t index 220d765b..302d23e5 100644 --- a/testcases/t/267-regress-mark-restart.t +++ b/testcases/t/267-regress-mark-restart.t @@ -20,6 +20,8 @@ use i3test; cmd 'open'; cmd 'mark foo'; +cmd 'open'; +cmd 'mark bar'; cmd 'restart'; From b48cbe42af94cf69700843922e57c68b81f3fddd Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Thu, 7 Sep 2017 03:53:28 +0300 Subject: [PATCH 035/123] Set marks to NULL after freeing realloc() was being called on an already freed pointer. Fixes #2900 --- src/load_layout.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/load_layout.c b/src/load_layout.c index 632c6ec7..7961e17f 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -157,6 +157,7 @@ static int json_end_map(void *ctx) { } free(marks); + marks = NULL; num_marks = 0; } From a542b3d26cb10f2d9f0850dcdec96653301d4c92 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Sep 2017 06:56:50 +0200 Subject: [PATCH 036/123] i3bar: ensure get_buffer does not leak memory This fixes an AddressSanitizer warning which recently popped up. related to #2907 --- i3bar/src/child.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 814f0411..fe989c44 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -333,10 +333,12 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) { break; } ELOG("read() failed!: %s\n", strerror(errno)); + FREE(buffer); exit(EXIT_FAILURE); } if (n == 0) { ELOG("stdin: received EOF\n"); + FREE(buffer); *ret_buffer_len = -1; return NULL; } From 4b0a6ba7696095db36f672cbca545dfd731fc85b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Sep 2017 08:15:03 +0200 Subject: [PATCH 037/123] travis: downgrade temporarily due to asan issue fixes #2912 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 63f69ac8..de9ff3fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ sudo: false dist: trusty +# TODO: remove “group” once trusty kernel is no longer affected by +# https://github.com/google/sanitizers/issues/837 +group: deprecated-2017Q3 services: - docker language: c From 083b6a31f42e7ffbe64eff5e810bdee5b3346e1e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Sep 2017 14:22:16 +0200 Subject: [PATCH 038/123] Include AnyEvent-I3 directory in dist tarballs (#2916) fixes #2905 --- Makefile.am | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Makefile.am b/Makefile.am index 3ea300e5..f1ba3f63 100644 --- a/Makefile.am +++ b/Makefile.am @@ -65,6 +65,19 @@ TESTS = testcases/complete-run.pl EXTRA_DIST = \ $(dist_docs_toc_DATA:.html=) \ $(dist_docs_notoc_DATA:.html=) \ + AnyEvent-I3/Changes \ + AnyEvent-I3/MANIFEST \ + AnyEvent-I3/MANIFEST.SKIP \ + AnyEvent-I3/Makefile.PL \ + AnyEvent-I3/README \ + AnyEvent-I3/lib/AnyEvent/I3.pm \ + AnyEvent-I3/t/00-load.t \ + AnyEvent-I3/t/01-workspaces.t \ + AnyEvent-I3/t/02-sugar.t \ + AnyEvent-I3/t/boilerplate.t \ + AnyEvent-I3/t/manifest.t \ + AnyEvent-I3/t/pod-coverage.t \ + AnyEvent-I3/t/pod.t \ docs/asciidoc-git.conf \ docs/bigpicture.png \ docs/i3-pod2html \ From 7cb9465db977ce7d88a7c215791b69de8f4e397f Mon Sep 17 00:00:00 2001 From: Orestis Date: Sat, 9 Sep 2017 15:47:32 +0300 Subject: [PATCH 039/123] Add files generated by make check in AnyEvent-I3/ to .gitignore (#2915) --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index c6fd81c8..b2ebd4aa 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,13 @@ testcases/MYMETA.json testcases/MYMETA.yml testcases/blib/ testcases/pm_to_blib +AnyEvent-I3/Makefile +AnyEvent-I3/META.yml +AnyEvent-I3/MYMETA.json +AnyEvent-I3/MYMETA.yml +AnyEvent-I3/blib/ +AnyEvent-I3/inc/ +AnyEvent-I3/pm_to_blib *.output *.tab.* *.yy.c From 155e307a3fe57f2b37a235ab4c8463638049080c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Sep 2017 11:25:43 +0200 Subject: [PATCH 040/123] testcases/Makefile.PL: tell MakeMaker this is a pure-Perl distribution (#2922) fixes #2914 --- testcases/Makefile.PL | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 0b1f3055..bf4e5dd7 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -18,7 +18,11 @@ WriteMakefile( PM => {}, # do not install any files from this directory clean => { FILES => 'testsuite-* latest i3-cfg-for-*', - } + }, + # This is a pure-Perl distribution: + linkext => { + LINKTYPE => '', + }, ); package MY; From 2eaf58a553e1cd468fdeb1823ab5170dac4bd324 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sun, 10 Sep 2017 11:02:10 +0000 Subject: [PATCH 041/123] docs/testsuite: Correct Xephyr package name on Arch Linux (#2913) The package is called `xorg-server-xephyr`, not `xorg-xserver-xephyr`. --- docs/testsuite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testsuite b/docs/testsuite index 795be042..4fa3e9e1 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -75,7 +75,7 @@ used to install the testsuite. Many users prefer to use the more modern +cpanminus+ instead, though (because it asks no questions and just works): The tests additionally require +Xephyr(1)+ to run a nested X server. Install -+xserver-xephyr+ on Debian or +xorg-xserver-xephyr+ on Arch Linux. ++xserver-xephyr+ on Debian or +xorg-server-xephyr+ on Arch Linux. .Installing testsuite dependencies using cpanminus (preferred) -------------------------------------------------------------------------------- From 429af6dbb35c6b70fec43efffad108bbc361c5f6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Sep 2017 19:41:49 +0200 Subject: [PATCH 042/123] tests: re-seed random number generator in workers --- testcases/lib/TestWorker.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testcases/lib/TestWorker.pm b/testcases/lib/TestWorker.pm index aee994f7..c56767c4 100644 --- a/testcases/lib/TestWorker.pm +++ b/testcases/lib/TestWorker.pm @@ -99,6 +99,11 @@ sub worker_wait { $0 = $file; + # Re-seed rand() so that File::Temp’s tempnam produces different + # results, making a TOCTOU between e.g. t/175-startup-notification.t + # and t/180-fd-leaks.t less likely. + srand(time ^ $$); + POSIX::dup2($ipc_fd, 0); POSIX::dup2($ipc_fd, 1); POSIX::dup2(1, 2); From 6dc164a65243e003712fbb4788a2977dfa427871 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Sep 2017 20:06:49 +0200 Subject: [PATCH 043/123] tests: unflake t/263-edge-borders.t --- testcases/t/263-edge-borders.t | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testcases/t/263-edge-borders.t b/testcases/t/263-edge-borders.t index 0d14c65c..6da1dacd 100644 --- a/testcases/t/263-edge-borders.t +++ b/testcases/t/263-edge-borders.t @@ -147,6 +147,10 @@ ok(@{get_ws_content($tmp)} == 2, 'after split & new window, two containers'); $wscontent = get_ws($tmp); +# Ensure i3’s X11 requests are processed before our inquiry via +# $tilewindow->rect: +sync_with_i3; + @tiled = @{$wscontent->{nodes}}; ok(@tiled == 2, 'two tiled container opened in another container'); is($tiled[0]->{current_border_width}, -1, 'first tiled current border width set to -1'); From 6203c6cb397ad978a0f24f2ea72ecdef54d49b50 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Sep 2017 20:05:55 +0200 Subject: [PATCH 044/123] tests: run 533-randr15.t at the very end The test runs `xrandr setmonitor`, which will otherwise affect any test scheduled after 533-randr15.t, causing flakyness in t/217-NET_CURRENT_DESKTOP.t for example. --- testcases/complete-run.pl.in | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index ba192469..ddd6ccad 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -177,11 +177,18 @@ my $timingsjson = slurp('.last_run_timings.json') if -e '.last_run_timings.json' map { [$_, $timings{$_} // 999] } @testfiles; # Run 000-load-deps.t first to bail out early when dependencies are missing. -my $loadtest = "t/000-load-deps.t"; -if ((scalar grep { $_ eq $loadtest } @testfiles) > 0) { +my ($loadtest) = grep { $_ =~ m,t/000-load-deps.t$, } @testfiles; +if (defined($loadtest)) { @testfiles = ($loadtest, grep { $_ ne $loadtest } @testfiles); } +# Run 533-randr15.t last because it destructively modifies the RandR +# configuration of the X session, interfering with any test started afterwards. +my ($randrtest) = grep { $_ =~ m,t/533-randr15.t$, } @testfiles; +if (defined($randrtest)) { + @testfiles = ((grep { $_ ne $randrtest } @testfiles), $randrtest); +} + printf("\nRough time estimate for this run: %.2f seconds\n\n", $timings{GLOBAL}) if exists($timings{GLOBAL}); From bf59c0fbfccf47ebfff549463cbf289649c42e93 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Mon, 11 Sep 2017 13:04:58 +0000 Subject: [PATCH 045/123] docs/hacking-howto: Promote "Using git / sending patches" section Move the contents of the "Using git / sending patches" section to the top of the document. --- docs/hacking-howto | 160 ++++++++++++++++++++++----------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index 52436da6..6842ce81 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -8,6 +8,86 @@ touching i3’s source code. It should contain all important information to help you understand why things are like they are. If it does not mention something you find necessary, please do not hesitate to contact me. +== Using git / sending patches + +=== Introduction + +For a short introduction into using git, see +http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy +or, for more documentation, see http://git-scm.com/documentation + +Please talk to us before working on new features to see whether they will be +accepted. A good way for this is to open an issue and asking for opinions on it. +Even for accepted features, this can be a good way to refine an idea upfront. However, +we don't want to see certain features in i3, e.g., switching window focus in an +Alt+Tab like way. + +When working on bugfixes, please make sure you mention that you are working on +it in the corresponding bug report at https://github.com/i3/i3/issues. In case +there is no bug report yet, please create one. + +After you are done, please submit your work for review as a pull request at +https://github.com/i3/i3. + +Do not send emails to the mailing list or any author directly, and don’t submit +them in the bugtracker, since all reviews should be done in public at +https://github.com/i3/i3. In order to make your review go as fast as possible, you +could have a look at previous reviews and see what the common mistakes are. + +=== Which branch to use? + +Work on i3 generally happens in two branches: “master” and “next” (the latter +being the default branch, the one that people get when they check out the git +repository). + +The contents of “master” are always stable. That is, it contains the source code +of the latest release, plus any bugfixes that were applied since that release. + +New features are only found in the “next” branch. Therefore, if you are working +on a new feature, use the “next” branch. If you are working on a bugfix, use the +“next” branch, too, but make sure your code also works on “master”. + +=== How to build? + +You can build i3 like you build any other software package which uses autotools. +Here’s a memory refresher: + + $ autoreconf -fi + $ mkdir -p build && cd build + $ ../configure + $ make -j8 + +(The autoreconf -fi step is unnecessary if you are building from a release tarball, + but shouldn’t hurt either.) + +==== Build system features + +* We use the AX_ENABLE_BUILDDIR macro to enforce builds happening in a separate + directory. This is a prerequisite for the AX_EXTEND_SRCDIR macro and building + in a separate directory is common practice anyway. In case this causes any + trouble when packaging i3 for your distribution, please open an issue. + +* “make check” runs the i3 testsuite. See docs/testsuite for details. + +* “make distcheck” (runs testsuite on “make dist” result, tiny bit quicker + feedback cycle than waiting for the travis build to catch the issue). + +* “make uninstall” (occasionally requested by users who compile from source) + +* “make” will build manpages/docs by default if the tools are installed. + Conversely, manpages/docs are not tried to be built for users who don’t want + to install all these dependencies to get started hacking on i3. + +* non-release builds will enable address sanitizer by default. Use the + --disable-sanitizers configure option to turn off all sanitizers, and see + --help for available sanitizers. + +* Support for pre-compiled headers (PCH) has been dropped for now in the + interest of simplicity. If you need support for PCH, please open an issue. + +* Coverage reports are now generated using “make check-code-coverage”, which + requires specifying --enable-code-coverage when calling configure. + == Window Managers A window manager is not necessarily needed to run X, but it is usually used in @@ -951,86 +1031,6 @@ Without much ado, here is the list of cases which need to be considered: not relative to workspace boundaries, so you must correct their coordinates or those containers will show up in the wrong workspace or not at all. -== Using git / sending patches - -=== Introduction - -For a short introduction into using git, see -http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy -or, for more documentation, see http://git-scm.com/documentation - -Please talk to us before working on new features to see whether they will be -accepted. A good way for this is to open an issue and asking for opinions on it. -Even for accepted features, this can be a good way to refine an idea upfront. However, -we don't want to see certain features in i3, e.g., switching window focus in an -Alt+Tab like way. - -When working on bugfixes, please make sure you mention that you are working on -it in the corresponding bug report at https://github.com/i3/i3/issues. In case -there is no bug report yet, please create one. - -After you are done, please submit your work for review as a pull request at -https://github.com/i3/i3. - -Do not send emails to the mailing list or any author directly, and don’t submit -them in the bugtracker, since all reviews should be done in public at -https://github.com/i3/i3. In order to make your review go as fast as possible, you -could have a look at previous reviews and see what the common mistakes are. - -=== Which branch to use? - -Work on i3 generally happens in two branches: “master” and “next” (the latter -being the default branch, the one that people get when they check out the git -repository). - -The contents of “master” are always stable. That is, it contains the source code -of the latest release, plus any bugfixes that were applied since that release. - -New features are only found in the “next” branch. Therefore, if you are working -on a new feature, use the “next” branch. If you are working on a bugfix, use the -“next” branch, too, but make sure your code also works on “master”. - -=== How to build? - -You can build i3 like you build any other software package which uses autotools. -Here’s a memory refresher: - - $ autoreconf -fi - $ mkdir -p build && cd build - $ ../configure - $ make -j8 - -(The autoreconf -fi step is unnecessary if you are building from a release tarball, - but shouldn’t hurt either.) - -==== Build system features - -* We use the AX_ENABLE_BUILDDIR macro to enforce builds happening in a separate - directory. This is a prerequisite for the AX_EXTEND_SRCDIR macro and building - in a separate directory is common practice anyway. In case this causes any - trouble when packaging i3 for your distribution, please open an issue. - -* “make check” runs the i3 testsuite. See docs/testsuite for details. - -* “make distcheck” (runs testsuite on “make dist” result, tiny bit quicker - feedback cycle than waiting for the travis build to catch the issue). - -* “make uninstall” (occasionally requested by users who compile from source) - -* “make” will build manpages/docs by default if the tools are installed. - Conversely, manpages/docs are not tried to be built for users who don’t want - to install all these dependencies to get started hacking on i3. - -* non-release builds will enable address sanitizer by default. Use the - --disable-sanitizers configure option to turn off all sanitizers, and see - --help for available sanitizers. - -* Support for pre-compiled headers (PCH) has been dropped for now in the - interest of simplicity. If you need support for PCH, please open an issue. - -* Coverage reports are now generated using “make check-code-coverage”, which - requires specifying --enable-code-coverage when calling configure. - == Thought experiments In this section, we collect thought experiments, so that we don’t forget our From 83d61e4b81aa576718b2d3c6065efea96b397869 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Mon, 11 Sep 2017 13:06:40 +0000 Subject: [PATCH 046/123] docs/hacking-howto: Promote "How to build?" sub-section Move the "How to build?" sub-section to the top of its parent section. --- docs/hacking-howto | 74 +++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index 6842ce81..9c89947b 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -10,43 +10,6 @@ you find necessary, please do not hesitate to contact me. == Using git / sending patches -=== Introduction - -For a short introduction into using git, see -http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy -or, for more documentation, see http://git-scm.com/documentation - -Please talk to us before working on new features to see whether they will be -accepted. A good way for this is to open an issue and asking for opinions on it. -Even for accepted features, this can be a good way to refine an idea upfront. However, -we don't want to see certain features in i3, e.g., switching window focus in an -Alt+Tab like way. - -When working on bugfixes, please make sure you mention that you are working on -it in the corresponding bug report at https://github.com/i3/i3/issues. In case -there is no bug report yet, please create one. - -After you are done, please submit your work for review as a pull request at -https://github.com/i3/i3. - -Do not send emails to the mailing list or any author directly, and don’t submit -them in the bugtracker, since all reviews should be done in public at -https://github.com/i3/i3. In order to make your review go as fast as possible, you -could have a look at previous reviews and see what the common mistakes are. - -=== Which branch to use? - -Work on i3 generally happens in two branches: “master” and “next” (the latter -being the default branch, the one that people get when they check out the git -repository). - -The contents of “master” are always stable. That is, it contains the source code -of the latest release, plus any bugfixes that were applied since that release. - -New features are only found in the “next” branch. Therefore, if you are working -on a new feature, use the “next” branch. If you are working on a bugfix, use the -“next” branch, too, but make sure your code also works on “master”. - === How to build? You can build i3 like you build any other software package which uses autotools. @@ -88,6 +51,43 @@ Here’s a memory refresher: * Coverage reports are now generated using “make check-code-coverage”, which requires specifying --enable-code-coverage when calling configure. +=== Introduction + +For a short introduction into using git, see +http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy +or, for more documentation, see http://git-scm.com/documentation + +Please talk to us before working on new features to see whether they will be +accepted. A good way for this is to open an issue and asking for opinions on it. +Even for accepted features, this can be a good way to refine an idea upfront. However, +we don't want to see certain features in i3, e.g., switching window focus in an +Alt+Tab like way. + +When working on bugfixes, please make sure you mention that you are working on +it in the corresponding bug report at https://github.com/i3/i3/issues. In case +there is no bug report yet, please create one. + +After you are done, please submit your work for review as a pull request at +https://github.com/i3/i3. + +Do not send emails to the mailing list or any author directly, and don’t submit +them in the bugtracker, since all reviews should be done in public at +https://github.com/i3/i3. In order to make your review go as fast as possible, you +could have a look at previous reviews and see what the common mistakes are. + +=== Which branch to use? + +Work on i3 generally happens in two branches: “master” and “next” (the latter +being the default branch, the one that people get when they check out the git +repository). + +The contents of “master” are always stable. That is, it contains the source code +of the latest release, plus any bugfixes that were applied since that release. + +New features are only found in the “next” branch. Therefore, if you are working +on a new feature, use the “next” branch. If you are working on a bugfix, use the +“next” branch, too, but make sure your code also works on “master”. + == Window Managers A window manager is not necessarily needed to run X, but it is usually used in From c3c94a8e1ae3c06dc1149ac697167bbfe10455d2 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Mon, 11 Sep 2017 13:09:25 +0000 Subject: [PATCH 047/123] docs/hacking-howto: Update section topology - Promote the "How to build?" sub-section to a top-level section ("Building i3") - Convert the "Introduction" sub-section as the intro to the remaining contents of the "Using git / sending patches" section - Keep "Which branch to use?" as a level-3 sub-section, thus making it a sub-section of what used to be the "Introduction" sub-section. --- docs/hacking-howto | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index 9c89947b..d585c2d7 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -8,9 +8,7 @@ touching i3’s source code. It should contain all important information to help you understand why things are like they are. If it does not mention something you find necessary, please do not hesitate to contact me. -== Using git / sending patches - -=== How to build? +== Building i3 You can build i3 like you build any other software package which uses autotools. Here’s a memory refresher: @@ -23,7 +21,7 @@ Here’s a memory refresher: (The autoreconf -fi step is unnecessary if you are building from a release tarball, but shouldn’t hurt either.) -==== Build system features +=== Build system features * We use the AX_ENABLE_BUILDDIR macro to enforce builds happening in a separate directory. This is a prerequisite for the AX_EXTEND_SRCDIR macro and building @@ -51,7 +49,7 @@ Here’s a memory refresher: * Coverage reports are now generated using “make check-code-coverage”, which requires specifying --enable-code-coverage when calling configure. -=== Introduction +== Using git / sending patches For a short introduction into using git, see http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy From 0631568b2d0831cd3a7c514755bfa144b0550642 Mon Sep 17 00:00:00 2001 From: Orestis Date: Mon, 11 Sep 2017 22:31:29 +0300 Subject: [PATCH 048/123] Fix userguide bug (#2932) Fixes #2931 --- docs/userguide | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 0d5de3b9..76982207 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1949,7 +1949,6 @@ bindsym $mod+x focus output HDMI-2 bindsym $mod+x focus output primary ------------------------------------------------- -------------------------------- Note that you might not have a primary output configured yet. To do so, run: ------------------------- xrandr --output --primary From d0c9e81f044f6f4343a5b020f7d7d68530af256b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 12 Sep 2017 22:16:36 +0200 Subject: [PATCH 049/123] testsuite: install Module::Install so that AnyEvent-I3/Makefile.PL works (#2940) As per https://perlmaven.com/cant-locate-inc-module-install-in-inc, the inc/ directory should not be under version control. fixes #2914 --- docs/testsuite | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/testsuite b/docs/testsuite index 4fa3e9e1..4f9cdbab 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -83,6 +83,7 @@ $ cd ~/i3/testcases $ sudo apt-get install cpanminus $ sudo cpanm . $ cd ~/i3/AnyEvent-I3 +$ sudo cpanm Module::Install $ sudo cpanm . -------------------------------------------------------------------------------- @@ -93,6 +94,7 @@ If you don’t want to use cpanminus for some reason, the same works with cpan: $ cd ~/i3/testcases $ sudo cpan . $ cd ~/i3/AnyEvent-I3 +$ sudo cpan Module::Install $ sudo cpan . -------------------------------------------------------------------------------- From 1b419431cd27695d65b2af364b53dd70a9cc436f Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 9 Sep 2017 07:18:29 +0000 Subject: [PATCH 050/123] Introduce output_primary_name function Currently simply returns output->name, but this will make it easier to change how output names are stored in the following commits. Also replace reading output->name with invocations of output_primary_name. Code which writes output->name is unchanged. Done using a mostly mechanical replacement of output->name to output_primary_name(output). --- include/output.h | 6 ++++++ src/con.c | 2 +- src/fake_outputs.c | 2 +- src/handlers.c | 2 +- src/ipc.c | 2 +- src/main.c | 2 +- src/manage.c | 2 +- src/output.c | 8 ++++++++ src/randr.c | 38 +++++++++++++++++++------------------- src/tree.c | 4 ++-- src/workspace.c | 4 ++-- src/xinerama.c | 2 +- 12 files changed, 44 insertions(+), 30 deletions(-) diff --git a/include/output.h b/include/output.h index b13c9728..31084da1 100644 --- a/include/output.h +++ b/include/output.h @@ -24,6 +24,12 @@ Con *output_get_content(Con *output); */ Output *get_output_from_string(Output *current_output, const char *output_str); +/** + * Retrieves the primary name of an output. + * + */ +char *output_primary_name(Output *output); + /** * Returns the output for the given con. * diff --git a/src/con.c b/src/con.c index b520c110..6bbe692f 100644 --- a/src/con.c +++ b/src/con.c @@ -1264,7 +1264,7 @@ void con_move_to_output(Con *con, Output *output) { Con *ws = NULL; GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); assert(ws != NULL); - DLOG("Moving con %p to output %s\n", con, output->name); + DLOG("Moving con %p to output %s\n", con, output_primary_name(output)); con_move_to_workspace(con, ws, false, false, false); } diff --git a/src/fake_outputs.c b/src/fake_outputs.c index b898ce98..93e00e72 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -49,7 +49,7 @@ void fake_outputs_init(const char *output_spec) { } else { new_output = scalloc(1, sizeof(Output)); sasprintf(&(new_output->name), "fake-%d", num_screens); - DLOG("Created new fake output %s (%p)\n", new_output->name, new_output); + DLOG("Created new fake output %s (%p)\n", output_primary_name(new_output), new_output); new_output->active = true; new_output->rect.x = x; new_output->rect.y = y; diff --git a/src/handlers.c b/src/handlers.c index c273e116..8d500fd9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -388,7 +388,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { Con *current_output = con_get_output(con); Output *target = get_output_containing(x, y); if (target != NULL && current_output != target->con) { - DLOG("Dock client is requested to be moved to output %s, moving it there.\n", target->name); + DLOG("Dock client is requested to be moved to output %s, moving it there.\n", output_primary_name(target)); Match *match; Con *nc = con_for_window(target->con, con->window, &match); DLOG("Dock client will be moved to container %p.\n", nc); diff --git a/src/ipc.c b/src/ipc.c index 18d6075d..b02b3fe4 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -829,7 +829,7 @@ IPC_HANDLER(get_outputs) { y(map_open); ystr("name"); - ystr(output->name); + ystr(output_primary_name(output)); ystr("active"); y(bool, output->active); diff --git a/src/main.c b/src/main.c index 21265446..44e4517e 100644 --- a/src/main.c +++ b/src/main.c @@ -687,7 +687,7 @@ int main(int argc, char *argv[]) { TAILQ_FOREACH(con, &(croot->nodes_head), nodes) { Output *output; TAILQ_FOREACH(output, &outputs, outputs) { - if (output->active || strcmp(con->name, output->name) != 0) + if (output->active || strcmp(con->name, output_primary_name(output)) != 0) continue; /* This will correctly correlate the output with its content diff --git a/src/manage.c b/src/manage.c index 86a361c3..004e8038 100644 --- a/src/manage.c +++ b/src/manage.c @@ -219,7 +219,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki LOG("This window is of type dock\n"); Output *output = get_output_containing(geom->x, geom->y); if (output != NULL) { - DLOG("Starting search at output %s\n", output->name); + DLOG("Starting search at output %s\n", output_primary_name(output)); search_at = output->con; } diff --git a/src/output.c b/src/output.c index e3c54a67..1b232694 100644 --- a/src/output.c +++ b/src/output.c @@ -44,6 +44,14 @@ Output *get_output_from_string(Output *current_output, const char *output_str) { return get_output_by_name(output_str, true); } +/* + * Retrieves the primary name of an output. + * + */ +char *output_primary_name(Output *output) { + return output->name; +} + Output *get_output_for_con(Con *con) { Con *output_con = con_get_output(con); if (output_con == NULL) { diff --git a/src/randr.c b/src/randr.c index 48bffb46..9cb45c5d 100644 --- a/src/randr.c +++ b/src/randr.c @@ -49,7 +49,7 @@ Output *get_output_by_name(const char *name, const bool require_active) { bool get_primary = (strcasecmp("primary", name) == 0); TAILQ_FOREACH(output, &outputs, outputs) { if ((output->primary && get_primary) || - ((!require_active || output->active) && strcasecmp(output->name, name) == 0)) { + ((!require_active || output->active) && strcasecmp(output_primary_name(output), name) == 0)) { return output; } } @@ -178,7 +178,7 @@ Output *get_output_next_wrap(direction_t direction, Output *current) { } if (!best) best = current; - DLOG("current = %s, best = %s\n", current->name, best->name); + DLOG("current = %s, best = %s\n", output_primary_name(current), output_primary_name(best)); return best; } @@ -250,7 +250,7 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far } } - DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL")); + DLOG("current = %s, best = %s\n", output_primary_name(current), (best ? output_primary_name(best) : "NULL")); return best; } @@ -280,12 +280,12 @@ void output_init_con(Output *output) { Con *con = NULL, *current; bool reused = false; - DLOG("init_con for output %s\n", output->name); + DLOG("init_con for output %s\n", output_primary_name(output)); /* Search for a Con with that name directly below the root node. There * might be one from a restored layout. */ TAILQ_FOREACH(current, &(croot->nodes_head), nodes) { - if (strcmp(current->name, output->name) != 0) + if (strcmp(current->name, output_primary_name(output)) != 0) continue; con = current; @@ -297,7 +297,7 @@ void output_init_con(Output *output) { if (con == NULL) { con = con_new(croot, NULL); FREE(con->name); - con->name = sstrdup(output->name); + con->name = sstrdup(output_primary_name(output)); con->type = CT_OUTPUT; con->layout = L_OUTPUT; con_fix_percent(croot); @@ -384,7 +384,7 @@ void init_ws_for_output(Output *output, Con *content) { /* go through all assignments and move the existing workspaces to this output */ struct Workspace_Assignment *assignment; TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (strcmp(assignment->output, output->name) != 0) + if (strcmp(assignment->output, output_primary_name(output)) != 0) continue; /* check if this workspace actually exists */ @@ -402,13 +402,13 @@ void init_ws_for_output(Output *output, Con *content) { LOG("Workspace \"%s\" assigned to output \"%s\", but it is already " "there. Do you have two assignment directives for the same " "workspace in your configuration file?\n", - workspace->name, output->name); + workspace->name, output_primary_name(output)); continue; } /* if so, move it over */ LOG("Moving workspace \"%s\" from output \"%s\" to \"%s\" due to assignment\n", - workspace->name, workspace_out->name, output->name); + workspace->name, workspace_out->name, output_primary_name(output)); /* if the workspace is currently visible on that output, we need to * switch to a different workspace - otherwise the output would end up @@ -445,7 +445,7 @@ void init_ws_for_output(Output *output, Con *content) { workspace_out->name); init_ws_for_output(get_output_by_name(workspace_out->name, true), output_get_content(workspace_out)); - DLOG("Done re-initializing, continuing with \"%s\"\n", output->name); + DLOG("Done re-initializing, continuing with \"%s\"\n", output_primary_name(output)); } } @@ -465,7 +465,7 @@ void init_ws_for_output(Output *output, Con *content) { /* otherwise, we create the first assigned ws for this output */ TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (strcmp(assignment->output, output->name) != 0) + if (strcmp(assignment->output, output_primary_name(output)) != 0) continue; LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n", @@ -652,7 +652,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, xcb_randr_get_output_info_name_length(output), xcb_randr_get_output_info_name(output)); - DLOG("found output with name %s\n", new->name); + DLOG("found output with name %s\n", output_primary_name(new)); /* Even if no CRTC is used at the moment, we store the output so that * we do not need to change the list ever again (we only update the @@ -672,7 +672,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) { DLOG("Skipping output %s: could not get CRTC (%p)\n", - new->name, crtc); + output_primary_name(new), crtc); free(new); return; } @@ -789,7 +789,7 @@ void randr_query_outputs(void) { if (!output->active || output->to_be_disabled) continue; DLOG("output %p / %s, position (%d, %d), checking for clones\n", - output, output->name, output->rect.x, output->rect.y); + output, output_primary_name(output), output->rect.x, output->rect.y); for (other = output; other != TAILQ_END(&outputs); @@ -813,7 +813,7 @@ void randr_query_outputs(void) { update_if_necessary(&(other->rect.width), width); update_if_necessary(&(other->rect.height), height); - DLOG("disabling output %p (%s)\n", other, other->name); + DLOG("disabling output %p (%s)\n", other, output_primary_name(other)); other->to_be_disabled = true; DLOG("new output mode %d x %d, other mode %d x %d\n", @@ -828,7 +828,7 @@ void randr_query_outputs(void) { * LVDS1 gets disabled. */ TAILQ_FOREACH(output, &outputs, outputs) { if (output->active && output->con == NULL) { - DLOG("Need to initialize a Con for output %s\n", output->name); + DLOG("Need to initialize a Con for output %s\n", output_primary_name(output)); output_init_con(output); output->changed = false; } @@ -854,7 +854,7 @@ void randr_query_outputs(void) { Con *content = output_get_content(output->con); if (!TAILQ_EMPTY(&(content->nodes_head))) continue; - DLOG("Should add ws for output %s\n", output->name); + DLOG("Should add ws for output %s\n", output_primary_name(output)); init_ws_for_output(output, content); } @@ -863,7 +863,7 @@ void randr_query_outputs(void) { if (!output->primary || !output->con) continue; - DLOG("Focusing primary output %s\n", output->name); + DLOG("Focusing primary output %s\n", output_primary_name(output)); con_focus(con_descend_focused(output->con)); } @@ -881,7 +881,7 @@ void randr_disable_output(Output *output) { assert(output->to_be_disabled); output->active = false; - DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); + DLOG("Output %s disabled, re-assigning workspaces/docks\n", output_primary_name(output)); Output *first = get_first_output(); diff --git a/src/tree.c b/src/tree.c index 2d4647f8..82a4756c 100644 --- a/src/tree.c +++ b/src/tree.c @@ -537,7 +537,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) if (!current_output) return false; - DLOG("Current output is %s\n", current_output->name); + DLOG("Current output is %s\n", output_primary_name(current_output)); /* Try to find next output */ direction_t direction; @@ -555,7 +555,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT); if (!next_output) return false; - DLOG("Next output is %s\n", next_output->name); + DLOG("Next output is %s\n", output_primary_name(next_output)); /* Find visible workspace on next output */ Con *workspace = NULL; diff --git a/src/workspace.c b/src/workspace.c index d7f2ce7c..4b350b82 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -188,7 +188,7 @@ Con *create_workspace_on_output(Output *output, Con *content) { struct Workspace_Assignment *assignment; TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { if (strcmp(assignment->name, target_name) != 0 || - strcmp(assignment->output, output->name) == 0) + strcmp(assignment->output, output_primary_name(output)) == 0) continue; assigned = true; @@ -935,7 +935,7 @@ bool workspace_move_to_output(Con *ws, const char *name) { bool used_assignment = false; struct Workspace_Assignment *assignment; TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (assignment->output == NULL || strcmp(assignment->output, current_output->name) != 0) + if (assignment->output == NULL || strcmp(assignment->output, output_primary_name(current_output)) != 0) continue; /* check if this workspace is already attached to the tree */ diff --git a/src/xinerama.c b/src/xinerama.c index 25bfa6b1..9574b894 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -56,7 +56,7 @@ static void query_screens(xcb_connection_t *conn) { } else { s = scalloc(1, sizeof(Output)); sasprintf(&(s->name), "xinerama-%d", num_screens); - DLOG("Created new Xinerama screen %s (%p)\n", s->name, s); + DLOG("Created new Xinerama screen %s (%p)\n", output_primary_name(s), s); s->active = true; s->rect.x = screen_info[screen].x_org; s->rect.y = screen_info[screen].y_org; From 6c0e715877164e4047e90e649c19f94ecfe8380e Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 9 Sep 2017 07:37:37 +0000 Subject: [PATCH 051/123] Store output names as a linked list Currently, only one name is ever added, and only the first name is ever accessed; actually using the capability to store and access multiple names comes in the following commits. --- include/data.h | 14 ++++++++++++-- src/fake_outputs.c | 5 ++++- src/output.c | 2 +- src/randr.c | 28 +++++++++++++++++++++++----- src/xinerama.c | 5 ++++- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/include/data.h b/include/data.h index 69a79ade..31ef1dc1 100644 --- a/include/data.h +++ b/include/data.h @@ -349,6 +349,13 @@ struct Autostart { autostarts_always; }; +struct output_name { + char *name; + + SLIST_ENTRY(output_name) + names; +}; + /** * An Output is a physical output on your graphics driver. Outputs which * are currently in use have (output->active == true). Each output has a @@ -370,8 +377,11 @@ struct xoutput { bool to_be_disabled; bool primary; - /** Name of the output */ - char *name; + /** List of names for the output. + * An output always has at least one name; the first name is + * considered the primary one. */ + SLIST_HEAD(names_head, output_name) + names_head; /** Pointer to the Con which represents this output */ Con *con; diff --git a/src/fake_outputs.c b/src/fake_outputs.c index 93e00e72..6639b361 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -47,8 +47,11 @@ void fake_outputs_init(const char *output_spec) { new_output->rect.width = min(new_output->rect.width, width); new_output->rect.height = min(new_output->rect.height, height); } else { + struct output_name *output_name = scalloc(1, sizeof(struct output_name)); new_output = scalloc(1, sizeof(Output)); - sasprintf(&(new_output->name), "fake-%d", num_screens); + sasprintf(&(output_name->name), "fake-%d", num_screens); + SLIST_INIT(&(new_output->names_head)); + SLIST_INSERT_HEAD(&(new_output->names_head), output_name, names); DLOG("Created new fake output %s (%p)\n", output_primary_name(new_output), new_output); new_output->active = true; new_output->rect.x = x; diff --git a/src/output.c b/src/output.c index 1b232694..e7690384 100644 --- a/src/output.c +++ b/src/output.c @@ -49,7 +49,7 @@ Output *get_output_from_string(Output *current_output, const char *output_str) { * */ char *output_primary_name(Output *output) { - return output->name; + return SLIST_FIRST(&output->names_head)->name; } Output *get_output_for_con(Con *con) { diff --git a/src/randr.c b/src/randr.c index 9cb45c5d..df63826d 100644 --- a/src/randr.c +++ b/src/randr.c @@ -266,7 +266,11 @@ Output *create_root_output(xcb_connection_t *conn) { s->rect.y = 0; s->rect.width = root_screen->width_in_pixels; s->rect.height = root_screen->height_in_pixels; - s->name = "xroot-0"; + + struct output_name *output_name = scalloc(1, sizeof(struct output_name)); + output_name->name = "xroot-0"; + SLIST_INIT(&s->names_head); + SLIST_INSERT_HEAD(&s->names_head, output_name, names); return s; } @@ -594,7 +598,12 @@ static bool randr_query_outputs_15(void) { Output *new = get_output_by_name(name, false); if (new == NULL) { new = scalloc(1, sizeof(Output)); - new->name = sstrdup(name); + + struct output_name *output_name = scalloc(1, sizeof(struct output_name)); + output_name->name = sstrdup(name); + SLIST_INIT(&new->names_head); + SLIST_INSERT_HEAD(&new->names_head, output_name, names); + if (monitor_info->primary) { TAILQ_INSERT_HEAD(&outputs, new, outputs); } else { @@ -643,14 +652,23 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, Output *new = get_output_by_id(id); bool existing = (new != NULL); - if (!existing) + if (!existing) { new = scalloc(1, sizeof(Output)); + SLIST_INIT(&new->names_head); + } new->id = id; new->primary = (primary && primary->output == id); - FREE(new->name); - sasprintf(&new->name, "%.*s", + while (!SLIST_EMPTY(&new->names_head)) { + FREE(SLIST_FIRST(&new->names_head)->name); + struct output_name *old_head = SLIST_FIRST(&new->names_head); + SLIST_REMOVE_HEAD(&new->names_head, names); + FREE(old_head); + } + struct output_name *output_name = scalloc(1, sizeof(struct output_name)); + sasprintf(&output_name->name, "%.*s", xcb_randr_get_output_info_name_length(output), xcb_randr_get_output_info_name(output)); + SLIST_INSERT_HEAD(&new->names_head, output_name, names); DLOG("found output with name %s\n", output_primary_name(new)); diff --git a/src/xinerama.c b/src/xinerama.c index 9574b894..d0651a85 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -55,7 +55,10 @@ static void query_screens(xcb_connection_t *conn) { s->rect.height = min(s->rect.height, screen_info[screen].height); } else { s = scalloc(1, sizeof(Output)); - sasprintf(&(s->name), "xinerama-%d", num_screens); + struct output_name *output_name = scalloc(1, sizeof(struct output_name)); + sasprintf(&output_name->name, "xinerama-%d", num_screens); + SLIST_INIT(&s->names_head); + SLIST_INSERT_HEAD(&s->names_head, output_name, names); DLOG("Created new Xinerama screen %s (%p)\n", output_primary_name(s), s); s->active = true; s->rect.x = screen_info[screen].x_org; From 08ad82c3bbc25f0c54df665ee7538e3980b0e51a Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 9 Sep 2017 09:00:22 +0000 Subject: [PATCH 052/123] randr: Register monitors' output names as additional i3 output names In addition to the name of the monitor itself (which is still used as the i3 output's primary name), register RandR output names associated with the RandR monitor as alternative i3 output names. --- src/randr.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index df63826d..92d652b7 100644 --- a/src/randr.c +++ b/src/randr.c @@ -599,9 +599,39 @@ static bool randr_query_outputs_15(void) { if (new == NULL) { new = scalloc(1, sizeof(Output)); + SLIST_INIT(&new->names_head); + + /* Register associated output names in addition to the monitor name */ + xcb_randr_output_t *randr_outputs = xcb_randr_monitor_info_outputs(monitor_info); + int randr_output_len = xcb_randr_monitor_info_outputs_length(monitor_info); + for (int i = 0; i < randr_output_len; i++) { + xcb_randr_output_t randr_output = randr_outputs[i]; + + xcb_randr_get_output_info_reply_t *info = + xcb_randr_get_output_info_reply(conn, + xcb_randr_get_output_info(conn, randr_output, monitors->timestamp), + NULL); + + if (info != NULL && info->crtc != XCB_NONE) { + char *oname; + sasprintf(&oname, "%.*s", + xcb_randr_get_output_info_name_length(info), + xcb_randr_get_output_info_name(info)); + + if (strcmp(name, oname) != 0) { + struct output_name *output_name = scalloc(1, sizeof(struct output_name)); + output_name->name = sstrdup(oname); + SLIST_INSERT_HEAD(&new->names_head, output_name, names); + } else { + free(oname); + } + } + FREE(info); + } + + /* Insert the monitor name last, so that it's used as the primary name */ struct output_name *output_name = scalloc(1, sizeof(struct output_name)); output_name->name = sstrdup(name); - SLIST_INIT(&new->names_head); SLIST_INSERT_HEAD(&new->names_head, output_name, names); if (monitor_info->primary) { From c35cacfd78711e3f5e8475220608523141988e44 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 9 Sep 2017 09:23:50 +0000 Subject: [PATCH 053/123] randr: Look up alternative output names when searching outputs Update get_output_by_name to look at all additional names added by the change in the previous commit, not just the primary one. --- src/randr.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/randr.c b/src/randr.c index 92d652b7..bc791696 100644 --- a/src/randr.c +++ b/src/randr.c @@ -48,10 +48,18 @@ Output *get_output_by_name(const char *name, const bool require_active) { Output *output; bool get_primary = (strcasecmp("primary", name) == 0); TAILQ_FOREACH(output, &outputs, outputs) { - if ((output->primary && get_primary) || - ((!require_active || output->active) && strcasecmp(output_primary_name(output), name) == 0)) { + if (output->primary && get_primary) { return output; } + if (require_active && !output->active) { + continue; + } + struct output_name *output_name; + SLIST_FOREACH(output_name, &output->names_head, names) { + if (strcasecmp(output_name->name, name) == 0) { + return output; + } + } } return NULL; From 16e0d5ec0603359f8dd72a2d91ff860cf97a8ee1 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Mon, 11 Sep 2017 11:15:56 +0000 Subject: [PATCH 054/123] ipc: Canonicalize output names in bar configuration Convert the output names specified in the "output" and "tray_output" fields in bar blocks in i3's configuration to the referred output's primary name. This allows specifying names other than the primary output's name in the given fields without changing the IPC protocol. --- src/ipc.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index b02b3fe4..274f6010 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -579,6 +579,11 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) { y(array_close); } +static char *canonicalize_output_name(char *name) { + Output *output = get_output_by_name(name, false); + return output ? output_primary_name(output) : name; +} + static void dump_bar_config(yajl_gen gen, Barconfig *config) { y(map_open); @@ -588,8 +593,13 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { if (config->num_outputs > 0) { ystr("outputs"); y(array_open); - for (int c = 0; c < config->num_outputs; c++) - ystr(config->outputs[c]); + for (int c = 0; c < config->num_outputs; c++) { + /* Convert monitor names (RandR ≥ 1.5) or output names + * (RandR < 1.5) into monitor names. This way, existing + * configs which use output names transparently keep + * working. */ + ystr(canonicalize_output_name(config->outputs[c])); + } y(array_close); } @@ -599,7 +609,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { struct tray_output_t *tray_output; TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) { - ystr(tray_output->output); + ystr(canonicalize_output_name(tray_output->output)); } y(array_close); From 38447ab78c39de46b07c5817d18e7804fee121ba Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 06:58:29 +0000 Subject: [PATCH 055/123] inject_randr1.5: Refactor reading and storing reply buffer to a struct Allows easier introduction of additional reply buffers in upcoming changes. --- testcases/inject_randr1.5.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index 5796ef05..b09510d0 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -41,9 +41,13 @@ void cleanup_socket(void) { } } +struct injected_reply { + void *buf; + off_t len; +}; + /* BEGIN RandR 1.5 specific */ -static void *injected_reply = NULL; -static off_t injected_reply_len = 0; +static struct injected_reply getmonitors_reply = {NULL, 0}; /* END RandR 1.5 specific */ #define XCB_PAD(i) (-(i)&3) @@ -294,10 +298,10 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { if (sequence == connstate->getmonitors) { printf("RRGetMonitors reply!\n"); - if (injected_reply != NULL) { + if (getmonitors_reply.buf != NULL) { printf("injecting reply\n"); - ((generic_x11_reply_t *)injected_reply)->sequence = sequence; - must_write(writeall(connstate->clientw->fd, injected_reply, injected_reply_len)); + ((generic_x11_reply_t *)getmonitors_reply.buf)->sequence = sequence; + must_write(writeall(connstate->clientw->fd, getmonitors_reply.buf, getmonitors_reply.len)); free(packet); return; } @@ -322,7 +326,7 @@ static void child_cb(EV_P_ ev_child *w, int revents) { } } -static void must_read_reply(const char *filename) { +static void must_read_reply(const char *filename, struct injected_reply *reply) { FILE *f; if ((f = fopen(filename, "r")) == NULL) { err(EXIT_FAILURE, "fopen(%s)", filename); @@ -331,11 +335,9 @@ static void must_read_reply(const char *filename) { if (fstat(fileno(f), &stbuf) != 0) { err(EXIT_FAILURE, "fstat(%s)", filename); } - /* BEGIN RandR 1.5 specific */ - injected_reply_len = stbuf.st_size; - injected_reply = smalloc(stbuf.st_size); - int n = fread(injected_reply, 1, stbuf.st_size, f); - /* END RandR 1.5 specific */ + reply->len = stbuf.st_size; + reply->buf = smalloc(stbuf.st_size); + int n = fread(reply->buf, 1, stbuf.st_size, f); if (n != stbuf.st_size) { err(EXIT_FAILURE, "fread(%s)", filename); } @@ -355,7 +357,7 @@ int main(int argc, char *argv[]) { switch (opt) { case 0: if (strcmp(long_options[option_index].name, "getmonitors_reply") == 0) { - must_read_reply(optarg); + must_read_reply(optarg, &getmonitors_reply); } break; default: From 80a937f43c26f72cb656ce51a84b97f18c6c38e3 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 07:03:20 +0000 Subject: [PATCH 056/123] inject_randr1.5: Add RRGetOutputInfo reply injection Add a --getoutputinfo_reply switch to indicate a filename containing the RRGetOutputInfo reply data to inject. --- testcases/inject_randr1.5.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index b09510d0..955df1e5 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -48,6 +48,7 @@ struct injected_reply { /* BEGIN RandR 1.5 specific */ static struct injected_reply getmonitors_reply = {NULL, 0}; +static struct injected_reply getoutputinfo_reply = {NULL, 0}; /* END RandR 1.5 specific */ #define XCB_PAD(i) (-(i)&3) @@ -70,6 +71,8 @@ struct connstate { int getext_randr; /* sequence number of the most recent RRGetMonitors request */ int getmonitors; + /* sequence number of the most recent RRGetOutputInfo request */ + int getoutputinfo; int randr_major_opcode; /* END RandR 1.5 specific */ @@ -263,6 +266,8 @@ static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) { const uint8_t randr_opcode = ((generic_x11_request_t *)request)->pad0; if (randr_opcode == XCB_RANDR_GET_MONITORS) { connstate->getmonitors = connstate->sequence; + } else if (randr_opcode == XCB_RANDR_GET_OUTPUT_INFO) { + connstate->getoutputinfo = connstate->sequence; } } /* END RandR 1.5 specific */ @@ -306,6 +311,17 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { return; } } + + if (sequence == connstate->getoutputinfo) { + printf("RRGetOutputInfo reply!\n"); + if (getoutputinfo_reply.buf != NULL) { + printf("injecting reply\n"); + ((generic_x11_reply_t *)getoutputinfo_reply.buf)->sequence = sequence; + must_write(writeall(connstate->clientw->fd, getoutputinfo_reply.buf, getoutputinfo_reply.len)); + free(packet); + return; + } + } /* END RandR 1.5 specific */ break; @@ -347,6 +363,7 @@ static void must_read_reply(const char *filename, struct injected_reply *reply) int main(int argc, char *argv[]) { static struct option long_options[] = { {"getmonitors_reply", required_argument, 0, 0}, + {"getoutputinfo_reply", required_argument, 0, 0}, {0, 0, 0, 0}, }; char *options_string = ""; @@ -355,11 +372,15 @@ int main(int argc, char *argv[]) { while ((opt = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (opt) { - case 0: - if (strcmp(long_options[option_index].name, "getmonitors_reply") == 0) { + case 0: { + const char *option_name = long_options[option_index].name; + if (strcmp(option_name, "getmonitors_reply") == 0) { must_read_reply(optarg, &getmonitors_reply); + } else if (strcmp(option_name, "getoutputinfo_reply") == 0) { + must_read_reply(optarg, &getoutputinfo_reply); } break; + } default: exit(EXIT_FAILURE); } From 7fb027ad376039431b74eb2f5708d6f9ef3cc7de Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 08:39:15 +0000 Subject: [PATCH 057/123] inject_randr1.5: Intercept X11 error responses in addition to replies Allow clients to send garbage to the server, then intercept the server's error response and substitute it with the supplied simulated reply data. --- testcases/inject_randr1.5.c | 62 +++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index 955df1e5..6cccfa76 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -276,6 +276,32 @@ static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) { free(request); } +static bool handle_sequence(struct connstate *connstate, uint16_t sequence) { + /* BEGIN RandR 1.5 specific */ + if (sequence == connstate->getmonitors) { + printf("RRGetMonitors reply!\n"); + if (getmonitors_reply.buf != NULL) { + printf("injecting reply\n"); + ((generic_x11_reply_t *)getmonitors_reply.buf)->sequence = sequence; + must_write(writeall(connstate->clientw->fd, getmonitors_reply.buf, getmonitors_reply.len)); + return true; + } + } + + if (sequence == connstate->getoutputinfo) { + printf("RRGetOutputInfo reply!\n"); + if (getoutputinfo_reply.buf != NULL) { + printf("injecting reply\n"); + ((generic_x11_reply_t *)getoutputinfo_reply.buf)->sequence = sequence; + must_write(writeall(connstate->clientw->fd, getoutputinfo_reply.buf, getoutputinfo_reply.len)); + return true; + } + } + /* END RandR 1.5 specific */ + + return false; +} + static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { struct connstate *connstate = (struct connstate *)w->data; // all packets from the server are at least 32 bytes in length @@ -283,9 +309,14 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { void *packet = smalloc(len); must_read(readall_into(packet, len, connstate->serverw->fd)); switch (((generic_x11_reply_t *)packet)->code) { - case 0: // error + case 0: { // error + const uint16_t sequence = ((xcb_request_error_t *)packet)->sequence; + if (handle_sequence(connstate, sequence)) { + free(packet); + return; + } break; - + } case 1: // reply len += ((generic_x11_reply_t *)packet)->length * 4; if (len > 32) { @@ -300,30 +331,13 @@ static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) { xcb_query_extension_reply_t *reply = packet; connstate->randr_major_opcode = reply->major_opcode; } - - if (sequence == connstate->getmonitors) { - printf("RRGetMonitors reply!\n"); - if (getmonitors_reply.buf != NULL) { - printf("injecting reply\n"); - ((generic_x11_reply_t *)getmonitors_reply.buf)->sequence = sequence; - must_write(writeall(connstate->clientw->fd, getmonitors_reply.buf, getmonitors_reply.len)); - free(packet); - return; - } - } - - if (sequence == connstate->getoutputinfo) { - printf("RRGetOutputInfo reply!\n"); - if (getoutputinfo_reply.buf != NULL) { - printf("injecting reply\n"); - ((generic_x11_reply_t *)getoutputinfo_reply.buf)->sequence = sequence; - must_write(writeall(connstate->clientw->fd, getoutputinfo_reply.buf, getoutputinfo_reply.len)); - free(packet); - return; - } - } /* END RandR 1.5 specific */ + if (handle_sequence(connstate, sequence)) { + free(packet); + return; + } + break; default: // event From 21dd8c475aec6c52898f4fe9b15dc4fa026089f8 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 08:41:00 +0000 Subject: [PATCH 058/123] testcases/lib: Add inject_randr15_outputinfo argument Allow tests to specify a file name for inject_randr15's --getoutputinfo_reply command-line parameter. --- testcases/lib/SocketActivation.pm | 7 ++++++- testcases/lib/i3test.pm.in | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 0f307eb3..5951fd26 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -145,7 +145,12 @@ sub activate_i3 { if ($args{inject_randr15}) { # See comment in $args{strace} branch. $cmd = 'test.inject_randr15 --getmonitors_reply="' . - $args{inject_randr15} . '" -- ' . + $args{inject_randr15} . '" ' . + ($args{inject_randr15_outputinfo} + ? '--getoutputinfo_reply="' . + $args{inject_randr15_outputinfo} . '" ' + : '') . + '-- ' . 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"'; } diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 18bebb52..f9b3e939 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -863,6 +863,7 @@ sub launch_with_config { dont_create_temp_dir => $args{dont_create_temp_dir}, validate_config => $args{validate_config}, inject_randr15 => $args{inject_randr15}, + inject_randr15_outputinfo => $args{inject_randr15_outputinfo}, ); # If we called i3 with -C, we wait for it to exit and then return as From f5e9b518da43680220639be393e96d924273d2c2 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 08:42:46 +0000 Subject: [PATCH 059/123] 533-randr15.t: Add a fake output connected to the fake monitor Add an output ID to the simulated RRGetMonitors reply, then add a simulated RRGetOutputInfo reply describing the added output. --- testcases/t/533-randr15.t | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t index 08fa88cc..da51a097 100644 --- a/testcases/t/533-randr15.t +++ b/testcases/t/533-randr15.t @@ -33,12 +33,12 @@ my ($outfh, $outname) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1); my $reply = pack('cxSLLLLx[LLL]', 1, # reply 0, # sequence (will be filled in by inject_randr15) - # 56 = length($reply) + length($monitor1) + # 60 = length($reply) + length($monitor1) # 32 = minimum X11 reply length - (56-32) / 4, # length in words + (60-32) / 4, # length in words 0, # timestamp TODO 1, # nmonitors - 0); # noutputs + 1); # noutputs # Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if # they are not yet interned. @@ -47,24 +47,45 @@ my $DP3 = $x->intern_atom_reply($atom_cookie->{sequence})->{atom}; # MONITORINFO is defined in A.1.1 in # https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt -my $monitor1 = pack('LccSssSSLL', +my $monitor1 = pack('LccSssSSLLL', $DP3, # name (ATOM) 1, # primary 1, # automatic - 0, # ncrtcs + 1, # ncrtcs 0, # x 0, # y 3840, # width in pixels 2160, # height in pixels 520, # width in millimeters - 290); # height in millimeters + 290, # height in millimeters + 12345); # output ID #0 print $outfh $reply; print $outfh $monitor1; close($outfh); -my $pid = launch_with_config($config, inject_randr15 => $outname); +# Prepare a RRGetOutputInfo reply as well; see RRGetOutputInfo in +# https://www.x.org/releases/current/doc/randrproto/randrproto.txt +my $output_name = 'i3-fake-output'; +($outfh, my $outname_moninfo) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1); +my $moninfo = pack('cxSLLLx[LLccSSSS]S a* x!4', + 1, # reply + 0, # sequence (will be filled in by inject_randr15) + # 36 = length($moninfo) (without name and padding) + # 32 = minimum X11 reply length + ((36 + length($output_name) - 32) + 3) / 4, # length in words + 0, # timestamp TODO + 12345, # CRTC + length($output_name), # length of name + $output_name); # name + +print $outfh $moninfo; +close($outfh); + +my $pid = launch_with_config($config, + inject_randr15 => $outname, + inject_randr15_outputinfo => $outname_moninfo); my $tree = i3->get_tree->recv; my @outputs = map { $_->{name} } @{$tree->{nodes}}; @@ -86,7 +107,7 @@ exit_gracefully($pid); # When inject_randr15 is defined but false, fake-xinerama will be turned off, # but inject_randr15 will not actually be used. -my $pid = launch_with_config($config, inject_randr15 => ''); +$pid = launch_with_config($config, inject_randr15 => ''); $tree = i3->get_tree->recv; @outputs = map { $_->{name} } @{$tree->{nodes}}; From fb1d9efb27d0a7daffaa11da2a2773e52405ddef Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 08:50:40 +0000 Subject: [PATCH 060/123] 533-randr15.t: Stop hard-coding the output name Refactor away all mentions of DP3. --- testcases/t/533-randr15.t | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t index da51a097..e22e8116 100644 --- a/testcases/t/533-randr15.t +++ b/testcases/t/533-randr15.t @@ -42,13 +42,14 @@ my $reply = pack('cxSLLLLx[LLL]', # Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if # they are not yet interned. -my $atom_cookie = $x->intern_atom(0, length("DP3"), "DP3"); -my $DP3 = $x->intern_atom_reply($atom_cookie->{sequence})->{atom}; +my $monitor_name = 'i3-fake-monitor'; +my $atom_cookie = $x->intern_atom(0, length($monitor_name), $monitor_name); +my $monitor_name_atom = $x->intern_atom_reply($atom_cookie->{sequence})->{atom}; # MONITORINFO is defined in A.1.1 in # https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt my $monitor1 = pack('LccSssSSLLL', - $DP3, # name (ATOM) + $monitor_name_atom, # name (ATOM) 1, # primary 1, # automatic 1, # ncrtcs @@ -89,15 +90,15 @@ my $pid = launch_with_config($config, my $tree = i3->get_tree->recv; my @outputs = map { $_->{name} } @{$tree->{nodes}}; -is_deeply(\@outputs, [ '__i3', 'DP3' ], 'outputs are __i3 and DP3'); +is_deeply(\@outputs, [ '__i3', $monitor_name ], 'outputs are __i3 and the fake monitor'); -my ($dp3) = grep { $_->{name} eq 'DP3' } @{$tree->{nodes}}; -is_deeply($dp3->{rect}, { +my ($output_data) = grep { $_->{name} eq $monitor_name } @{$tree->{nodes}}; +is_deeply($output_data->{rect}, { width => 3840, height => 2160, x => 0, y => 0, - }, 'Output DP3 at 3840x2160+0+0'); + }, "Fake output at 3840x2160+0+0"); exit_gracefully($pid); From 92ff6fbe24dd7c66e0cea30ebd70ca812170a1db Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 09:18:40 +0000 Subject: [PATCH 061/123] 533-randr15.t: Add test for bar output name canonicalization --- testcases/t/533-randr15.t | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t index e22e8116..5b81194a 100644 --- a/testcases/t/533-randr15.t +++ b/testcases/t/533-randr15.t @@ -20,10 +20,16 @@ use File::Temp qw(tempfile); use i3test i3_autostart => 0; +my $monitor_name = 'i3-fake-monitor'; +my $output_name = 'i3-fake-output'; + my $config = < 1); @@ -42,7 +48,6 @@ my $reply = pack('cxSLLLLx[LLL]', # Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if # they are not yet interned. -my $monitor_name = 'i3-fake-monitor'; my $atom_cookie = $x->intern_atom(0, length($monitor_name), $monitor_name); my $monitor_name_atom = $x->intern_atom_reply($atom_cookie->{sequence})->{atom}; @@ -68,7 +73,6 @@ close($outfh); # Prepare a RRGetOutputInfo reply as well; see RRGetOutputInfo in # https://www.x.org/releases/current/doc/randrproto/randrproto.txt -my $output_name = 'i3-fake-output'; ($outfh, my $outname_moninfo) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1); my $moninfo = pack('cxSLLLx[LLccSSSS]S a* x!4', 1, # reply @@ -100,6 +104,17 @@ is_deeply($output_data->{rect}, { y => 0, }, "Fake output at 3840x2160+0+0"); +# Verify that i3 canonicalizes RandR output names to i3 output names +# (RandR monitor names) for bar configs + +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}, [ $monitor_name ], 'bar_config output name is normalized'); + exit_gracefully($pid); ################################################################################ From 2159306b941330438ed4c1d4f406d4ccacc34c2c Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 12 Sep 2017 09:27:49 +0000 Subject: [PATCH 062/123] docs/userguide: Document that i3 can accept RandR output names --- docs/userguide | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/userguide b/docs/userguide index 76982207..34159304 100644 --- a/docs/userguide +++ b/docs/userguide @@ -869,6 +869,18 @@ The 'output' is the name of the RandR output you attach your screen to. On a laptop, you might have VGA1 and LVDS1 as output names. You can see the available outputs by running +xrandr --current+. +If your X server supports RandR 1.5 or newer, i3 will use RandR monitor objects +instead of output objects. Run +xrandr --listmonitors+ to see a list. Usually, +a monitor object contains exactly one output, and has the same name as the +output; but should that not be the case, you may specify the name of either the +monitor or the output in i3's configuration. For example, the Dell UP2414Q uses +two scalers internally, so its output names might be “DP1” and “DP2”, but the +monitor name is “Dell UP2414Q”. + +(Note that even if you specify the name of an output which doesn't span the +entire monitor, i3 will still use the entire area of the containing monitor +rather than that of just the output's.) + If you use named workspaces, they must be quoted: *Examples*: From 919ac9c7ef4be41bd3c2ef923cd38ba523d1f8c3 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 13 Sep 2017 13:32:12 +0300 Subject: [PATCH 063/123] Don't insert newline at end of config with launch_with_config --- testcases/lib/i3test.pm.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index f9b3e939..ca64edfd 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -834,8 +834,11 @@ sub launch_with_config { my ($fh, $tmpfile) = tempfile("i3-cfg-for-$ENV{TESTNAME}-XXXXX", UNLINK => 1); + say $fh "ipc-socket $tmp_socket_path" + unless $args{dont_add_socket_path}; + if ($config ne '-default') { - say $fh $config; + print $fh $config; } else { open(my $conf_fh, '<', '@abs_top_srcdir@/testcases/i3-test.config') or $tester->BAIL_OUT("could not open default config: $!"); @@ -843,9 +846,6 @@ sub launch_with_config { say $fh scalar <$conf_fh>; } - say $fh "ipc-socket $tmp_socket_path" - unless $args{dont_add_socket_path}; - close($fh); my $cv = AnyEvent->condvar; From d35de66f1e35f6c6ad6df5ad33c44541e19a5cdf Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 12 Sep 2017 12:29:01 +0300 Subject: [PATCH 064/123] scalloc parse_config input to make sure it terminates with '\0' Otherwise strchr() can crash for files that don't end with '\n' because it won't find a null char to terminate at. Fixes #2934 --- src/config_parser.c | 2 +- testcases/t/270-config-no-newline-end.t | 41 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 testcases/t/270-config-no-newline-end.t diff --git a/src/config_parser.c b/src/config_parser.c index c88e9d1e..58a5552c 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1025,7 +1025,7 @@ bool parse_file(const char *f, bool use_nagbar) { /* Then, allocate a new buffer and copy the file over to the new one, * but replace occurrences of our variables */ char *walk = buf, *destwalk; - char *new = smalloc(stbuf.st_size + extra_bytes + 1); + char *new = scalloc(stbuf.st_size + extra_bytes + 1, 1); destwalk = new; while (walk < (buf + stbuf.st_size)) { /* Find the next variable */ diff --git a/testcases/t/270-config-no-newline-end.t b/testcases/t/270-config-no-newline-end.t new file mode 100644 index 00000000..4e7ccbf7 --- /dev/null +++ b/testcases/t/270-config-no-newline-end.t @@ -0,0 +1,41 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Make sure that configs that end without a newline don't crash i3. +# Ticket: #2934 +use i3test i3_autostart => 0; + +my $first_lines = <<'EOT'; +set $workspace1 workspace number 1 +set $workspace0 workspace eggs + +bindsym Mod4+1 $workspace1 +EOT + +# Intentionally don't add a trailing newline for the last line since this is +# what triggered the bug. +my $last_line = 'bindsym Mod4+0 $workspace0'; +my $config = "${first_lines}${last_line}"; + +my $pid = launch_with_config($config); +does_i3_live; + +my $i3 = i3(get_socket_path()); +my $ws = $i3->get_workspaces->recv; +is($ws->[0]->{name}, 'eggs', 'last line processed correctly'); + +exit_gracefully($pid); +done_testing; From f120a9d929fd9ad64dd26813967e7b0a03b1390b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Sep 2017 16:39:13 +0200 Subject: [PATCH 065/123] Bugfix: free incomplete containers when JSON parsing fails related to #2755 --- include/con.h | 6 ++ src/con.c | 24 +++++++ src/load_layout.c | 13 ++++ src/tree.c | 17 +---- testcases/t/215-layout-restore-crash.t | 93 +++++++++++++++++++++++++- 5 files changed, 135 insertions(+), 18 deletions(-) diff --git a/include/con.h b/include/con.h index 1c7bb932..6a7e31bc 100644 --- a/include/con.h +++ b/include/con.h @@ -25,6 +25,12 @@ Con *con_new_skeleton(Con *parent, i3Window *window); */ Con *con_new(Con *parent, i3Window *window); +/** + * Frees the specified container. + * + */ +void con_free(Con *con); + /** * Sets input focus to the given container. Will be updated in X11 in the next * run of x_push_changes(). diff --git a/src/con.c b/src/con.c index 6bbe692f..9797afa6 100644 --- a/src/con.c +++ b/src/con.c @@ -73,6 +73,30 @@ Con *con_new(Con *parent, i3Window *window) { return new; } +/* + * Frees the specified container. + * + */ +void con_free(Con *con) { + free(con->name); + FREE(con->deco_render_params); + TAILQ_REMOVE(&all_cons, con, all_cons); + while (!TAILQ_EMPTY(&(con->swallow_head))) { + Match *match = TAILQ_FIRST(&(con->swallow_head)); + TAILQ_REMOVE(&(con->swallow_head), match, matches); + match_free(match); + free(match); + } + while (!TAILQ_EMPTY(&(con->marks_head))) { + mark_t *mark = TAILQ_FIRST(&(con->marks_head)); + TAILQ_REMOVE(&(con->marks_head), mark, marks); + FREE(mark->name); + FREE(mark); + } + free(con); + DLOG("con %p freed\n", con); +} + static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) { con->parent = parent; Con *loop; diff --git a/src/load_layout.c b/src/load_layout.c index 7961e17f..0fa3e85b 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -18,6 +18,7 @@ /* TODO: refactor the whole parsing thing */ static char *last_key; +static int incomplete; static Con *json_node; static Con *to_focus; static bool parsing_swallows; @@ -68,6 +69,9 @@ static int json_start_map(void *ctx) { json_node->name = NULL; json_node->parent = parent; } + /* json_node is incomplete and should be removed if parsing fails */ + incomplete++; + DLOG("incomplete = %d\n", incomplete); } } return 1; @@ -166,6 +170,8 @@ static int json_end_map(void *ctx) { LOG("Creating window\n"); x_con_init(json_node); json_node = json_node->parent; + incomplete--; + DLOG("incomplete = %d\n", incomplete); } if (parsing_swallows && swallow_is_empty) { @@ -634,6 +640,7 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { yajl_status stat; json_node = con; to_focus = NULL; + incomplete = 0; parsing_swallows = false; parsing_rect = false; parsing_deco_rect = false; @@ -649,6 +656,12 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { if (errormsg != NULL) *errormsg = sstrdup((const char *)str); yajl_free_error(hand, str); + while (incomplete-- > 0) { + Con *parent = json_node->parent; + DLOG("freeing incomplete container %p\n", json_node); + con_free(json_node); + json_node = parent; + } } /* In case not all containers were restored, we need to fix the diff --git a/src/tree.c b/src/tree.c index 82a4756c..296a6a37 100644 --- a/src/tree.c +++ b/src/tree.c @@ -320,22 +320,7 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par DLOG("parent container killed\n"); } - free(con->name); - FREE(con->deco_render_params); - TAILQ_REMOVE(&all_cons, con, all_cons); - while (!TAILQ_EMPTY(&(con->swallow_head))) { - Match *match = TAILQ_FIRST(&(con->swallow_head)); - TAILQ_REMOVE(&(con->swallow_head), match, matches); - match_free(match); - free(match); - } - while (!TAILQ_EMPTY(&(con->marks_head))) { - mark_t *mark = TAILQ_FIRST(&(con->marks_head)); - TAILQ_REMOVE(&(con->marks_head), mark, marks); - FREE(mark->name); - FREE(mark); - } - free(con); + con_free(con); /* in the case of floating windows, we already focused another container * when closing the parent, so we can exit now. */ diff --git a/testcases/t/215-layout-restore-crash.t b/testcases/t/215-layout-restore-crash.t index 4430dac8..5b34c29c 100644 --- a/testcases/t/215-layout-restore-crash.t +++ b/testcases/t/215-layout-restore-crash.t @@ -131,14 +131,103 @@ print $fh <<'EOT'; EOT $fh->flush; my $reply = cmd "append_layout $filename"; -diag('reply = ' . Dumper($reply)); +ok(!$reply->[0]->{success}, 'IPC reply did not indicate success'); does_i3_live; -ok(!$reply->[0]->{success}, 'IPC reply did not indicate success'); close($fh); +################################################################################ +# another file with a superfluous trailing comma (issue #2755) +################################################################################ + +subtest 'issue 2755' => sub { + plan tests => 4; + $ws = fresh_workspace; + + @content = @{get_ws_content($ws)}; + is(@content, 0, 'no nodes on the new workspace yet'); + + ($fh, $filename) = tempfile(UNLINK => 1); + print $fh <<'EOT'; +// vim:ts=4:sw=4:et +{ + // splith split container with 2 children + "border": "normal", + "floating": "auto_off", + "layout": "splith", + "percent": null, + "type": "con", + "nodes": [ + { + "border": "normal", + "current_border_width": 2, + "floating": "auto_off", + "geometry": { + "height": 860, + "width": 1396, + "x": 1922, + "y": 38 + }, + "name": "Chromium1", + "percent": 0.5, + "swallows": [ + { + "class": "^Chromium$", + // "instance": "^chromium$", + // "title": "^Git\\ Tutorial\\ \\-\\ corp\\ \\-\\ Chromium$", + // "transient_for": "^$", + // "window_role": "^browser$" + } + ], + "type": "con" + }, + { + "border": "normal", + "current_border_width": 2, + "floating": "auto_off", + "geometry": { + "height": 1040, + "width": 956, + "x": 2, + "y": 38 + }, + "name": "Chromium2", + "percent": 0.5, + "swallows": [ + { + "class": "^Chromium$", + // "instance": "^chromium$", + // "title": "^Nutanix\\ \\-\\ Prod\\ \\-\\ Sign\\ In\\ \\-\\ Chromium$", + // "transient_for": "^$", + // "window_role": "^browser$" + } + ], + "type": "con" + } + ] +} + +EOT + $fh->flush; + $reply = cmd "append_layout $filename"; + ok(!$reply->[0]->{success}, 'IPC reply indicated success'); + + does_i3_live; + + # Move to a different workspace rendered the half-attached con’s con->parent + # invalid. + fresh_workspace; + + cmd '[urgent=latest] focus'; + $reply = cmd 'scratchpad show'; + + does_i3_live; + + close($fh); +}; + ################################################################################ # wrong percent key in a child node ################################################################################ From 51da57d5e6965194b454b63d8f08d9c7e3b747d6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 13 Sep 2017 17:14:51 +0200 Subject: [PATCH 066/123] validate JSON before loading This commit also introduces slurp() which reads a file in its entirety. Using this function instead of doing IO in the functions in load_layout.c again and again makes the code cleaner (fixing at least two memory leaks) and avoids re-reading the same file 3 times. related to #2755 --- include/load_layout.h | 10 +++- include/util.h | 8 +++ src/commands.c | 24 +++++++-- src/load_layout.c | 120 +++++++++++++++++------------------------- src/tree.c | 20 +++++-- src/util.c | 32 +++++++++++ 6 files changed, 131 insertions(+), 83 deletions(-) diff --git a/include/load_layout.h b/include/load_layout.h index 0dd81318..9205800f 100644 --- a/include/load_layout.h +++ b/include/load_layout.h @@ -31,6 +31,12 @@ typedef enum { * determine whether the file contains workspaces or regular containers, which * is important to know when deciding where (and how) to append the contents. * */ -json_content_t json_determine_content(const char *filename); +json_content_t json_determine_content(const char *buf, const size_t len); -void tree_append_json(Con *con, const char *filename, char **errormsg); +/** + * Returns true if the provided JSON could be parsed by yajl. + * + */ +bool json_validate(const char *buf, const size_t len); + +void tree_append_json(Con *con, const char *buf, const size_t len, char **errormsg); diff --git a/include/util.h b/include/util.h index 6c5fc4c6..de6fa568 100644 --- a/include/util.h +++ b/include/util.h @@ -164,3 +164,11 @@ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it); * if the number could be parsed. */ bool parse_long(const char *str, long *out, int base); + +/** + * Slurp reads path in its entirety into buf, returning the length of the file + * or -1 if the file could not be read. buf is set to a buffer of appropriate + * size, or NULL if -1 is returned. + * + */ +ssize_t slurp(const char *path, char **buf); diff --git a/src/commands.c b/src/commands.c index faee3916..2697d6e1 100644 --- a/src/commands.c +++ b/src/commands.c @@ -773,13 +773,25 @@ void cmd_append_layout(I3_CMD, const char *cpath) { /* Make sure we allow paths like '~/.i3/layout.json' */ path = resolve_tilde(path); - json_content_t content = json_determine_content(path); + char *buf = NULL; + ssize_t len; + if ((len = slurp(path, &buf)) < 0) { + /* slurp already logged an error. */ + goto out; + } + + if (!json_validate(buf, len)) { + ELOG("Could not parse \"%s\" as JSON, not loading.\n", path); + yerror("Could not parse \"%s\" as JSON.", path); + goto out; + } + + json_content_t content = json_determine_content(buf, len); LOG("JSON content = %d\n", content); if (content == JSON_CONTENT_UNKNOWN) { ELOG("Could not determine the contents of \"%s\", not loading.\n", path); yerror("Could not determine the contents of \"%s\".", path); - free(path); - return; + goto out; } Con *parent = focused; @@ -795,7 +807,7 @@ void cmd_append_layout(I3_CMD, const char *cpath) { } DLOG("Appending to parent=%p instead of focused=%p\n", parent, focused); char *errormsg = NULL; - tree_append_json(parent, path, &errormsg); + tree_append_json(parent, buf, len, &errormsg); if (errormsg != NULL) { yerror(errormsg); free(errormsg); @@ -820,8 +832,10 @@ void cmd_append_layout(I3_CMD, const char *cpath) { if (content == JSON_CONTENT_WORKSPACE) ipc_send_workspace_event("restored", parent, NULL); - free(path); cmd_output->needs_tree_render = true; +out: + free(path); + free(buf); } /* diff --git a/src/load_layout.c b/src/load_layout.c index 0fa3e85b..071b3ccd 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -69,9 +69,9 @@ static int json_start_map(void *ctx) { json_node->name = NULL; json_node->parent = parent; } - /* json_node is incomplete and should be removed if parsing fails */ - incomplete++; - DLOG("incomplete = %d\n", incomplete); + /* json_node is incomplete and should be removed if parsing fails */ + incomplete++; + DLOG("incomplete = %d\n", incomplete); } } return 1; @@ -170,8 +170,8 @@ static int json_end_map(void *ctx) { LOG("Creating window\n"); x_con_init(json_node); json_node = json_node->parent; - incomplete--; - DLOG("incomplete = %d\n", incomplete); + incomplete--; + DLOG("incomplete = %d\n", incomplete); } if (parsing_swallows && swallow_is_empty) { @@ -538,36 +538,42 @@ static int json_determine_content_string(void *ctx, const unsigned char *val, si return 0; } +/* + * Returns true if the provided JSON could be parsed by yajl. + * + */ +bool json_validate(const char *buf, const size_t len) { + bool valid = true; + yajl_handle hand = yajl_alloc(NULL, NULL, NULL); + /* Allowing comments allows for more user-friendly layout files. */ + yajl_config(hand, yajl_allow_comments, true); + /* Allow multiple values, i.e. multiple nodes to attach */ + yajl_config(hand, yajl_allow_multiple_values, true); + + setlocale(LC_NUMERIC, "C"); + if (yajl_parse(hand, (const unsigned char *)buf, len) != yajl_status_ok) { + unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, len); + ELOG("JSON parsing error: %s\n", str); + yajl_free_error(hand, str); + valid = false; + } + setlocale(LC_NUMERIC, ""); + + yajl_complete_parse(hand); + yajl_free(hand); + + return valid; +} + /* Parses the given JSON file until it encounters the first “type” property to * determine whether the file contains workspaces or regular containers, which * is important to know when deciding where (and how) to append the contents. * */ -json_content_t json_determine_content(const char *filename) { - FILE *f; - if ((f = fopen(filename, "r")) == NULL) { - ELOG("Cannot open file \"%s\"\n", filename); - return JSON_CONTENT_UNKNOWN; - } - struct stat stbuf; - if (fstat(fileno(f), &stbuf) != 0) { - ELOG("Cannot fstat() \"%s\"\n", filename); - fclose(f); - return JSON_CONTENT_UNKNOWN; - } - char *buf = smalloc(stbuf.st_size); - int n = fread(buf, 1, stbuf.st_size, f); - if (n != stbuf.st_size) { - ELOG("File \"%s\" could not be read entirely, not loading.\n", filename); - fclose(f); - return JSON_CONTENT_UNKNOWN; - } - DLOG("read %d bytes\n", n); +json_content_t json_determine_content(const char *buf, const size_t len) { // We default to JSON_CONTENT_CON because it is legal to not include // “"type": "con"” in the JSON files for better readability. content_result = JSON_CONTENT_CON; content_level = 0; - yajl_gen g; - yajl_handle hand; static yajl_callbacks callbacks = { .yajl_string = json_determine_content_string, .yajl_map_key = json_key, @@ -576,51 +582,27 @@ json_content_t json_determine_content(const char *filename) { .yajl_end_map = json_determine_content_shallower, .yajl_end_array = json_determine_content_shallower, }; - g = yajl_gen_alloc(NULL); - hand = yajl_alloc(&callbacks, NULL, (void *)g); + yajl_handle hand = yajl_alloc(&callbacks, NULL, NULL); /* Allowing comments allows for more user-friendly layout files. */ yajl_config(hand, yajl_allow_comments, true); /* Allow multiple values, i.e. multiple nodes to attach */ yajl_config(hand, yajl_allow_multiple_values, true); - yajl_status stat; setlocale(LC_NUMERIC, "C"); - stat = yajl_parse(hand, (const unsigned char *)buf, n); + const yajl_status stat = yajl_parse(hand, (const unsigned char *)buf, len); if (stat != yajl_status_ok && stat != yajl_status_client_canceled) { - unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, n); + unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, len); ELOG("JSON parsing error: %s\n", str); yajl_free_error(hand, str); } setlocale(LC_NUMERIC, ""); yajl_complete_parse(hand); - - fclose(f); + yajl_free(hand); return content_result; } -void tree_append_json(Con *con, const char *filename, char **errormsg) { - FILE *f; - if ((f = fopen(filename, "r")) == NULL) { - ELOG("Cannot open file \"%s\"\n", filename); - return; - } - struct stat stbuf; - if (fstat(fileno(f), &stbuf) != 0) { - ELOG("Cannot fstat() \"%s\"\n", filename); - fclose(f); - return; - } - char *buf = smalloc(stbuf.st_size); - int n = fread(buf, 1, stbuf.st_size, f); - if (n != stbuf.st_size) { - ELOG("File \"%s\" could not be read entirely, not loading.\n", filename); - fclose(f); - return; - } - DLOG("read %d bytes\n", n); - yajl_gen g; - yajl_handle hand; +void tree_append_json(Con *con, const char *buf, const size_t len, char **errormsg) { static yajl_callbacks callbacks = { .yajl_boolean = json_bool, .yajl_integer = json_int, @@ -631,13 +613,11 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { .yajl_end_map = json_end_map, .yajl_end_array = json_end_array, }; - g = yajl_gen_alloc(NULL); - hand = yajl_alloc(&callbacks, NULL, (void *)g); + yajl_handle hand = yajl_alloc(&callbacks, NULL, NULL); /* Allowing comments allows for more user-friendly layout files. */ yajl_config(hand, yajl_allow_comments, true); /* Allow multiple values, i.e. multiple nodes to attach */ yajl_config(hand, yajl_allow_multiple_values, true); - yajl_status stat; json_node = con; to_focus = NULL; incomplete = 0; @@ -649,19 +629,19 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { parsing_focus = false; parsing_marks = false; setlocale(LC_NUMERIC, "C"); - stat = yajl_parse(hand, (const unsigned char *)buf, n); + const yajl_status stat = yajl_parse(hand, (const unsigned char *)buf, len); if (stat != yajl_status_ok) { - unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, n); + unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, len); ELOG("JSON parsing error: %s\n", str); if (errormsg != NULL) *errormsg = sstrdup((const char *)str); yajl_free_error(hand, str); - while (incomplete-- > 0) { - Con *parent = json_node->parent; - DLOG("freeing incomplete container %p\n", json_node); - con_free(json_node); - json_node = parent; - } + while (incomplete-- > 0) { + Con *parent = json_node->parent; + DLOG("freeing incomplete container %p\n", json_node); + con_free(json_node); + json_node = parent; + } } /* In case not all containers were restored, we need to fix the @@ -672,10 +652,8 @@ void tree_append_json(Con *con, const char *filename, char **errormsg) { setlocale(LC_NUMERIC, ""); yajl_complete_parse(hand); yajl_free(hand); - yajl_gen_free(g); - fclose(f); - free(buf); - if (to_focus) + if (to_focus) { con_focus(to_focus); + } } diff --git a/src/tree.c b/src/tree.c index 296a6a37..b3d2ce93 100644 --- a/src/tree.c +++ b/src/tree.c @@ -64,12 +64,19 @@ static Con *_create___i3(void) { * */ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { + bool result = false; char *globbed = resolve_tilde(path); if (!path_exists(globbed)) { LOG("%s does not exist, not restoring tree\n", globbed); - free(globbed); - return false; + goto out; + } + + char *buf = NULL; + ssize_t len; + if ((len = slurp(globbed, &buf)) < 0) { + /* slurp already logged an error. */ + goto out; } /* TODO: refactor the following */ @@ -81,8 +88,7 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { geometry->height}; focused = croot; - tree_append_json(focused, globbed, NULL); - free(globbed); + tree_append_json(focused, buf, len, NULL); DLOG("appended tree, using new root\n"); croot = TAILQ_FIRST(&(croot->nodes_head)); @@ -104,8 +110,12 @@ bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { } restore_open_placeholder_windows(croot); + result = true; - return true; +out: + free(globbed); + free(buf); + return result; } /* diff --git a/src/util.c b/src/util.c index 32c3c57e..cd5ee03e 100644 --- a/src/util.c +++ b/src/util.c @@ -475,3 +475,35 @@ bool parse_long(const char *str, long *out, int base) { *out = result; return true; } + +/* + * Slurp reads path in its entirety into buf, returning the length of the file + * or -1 if the file could not be read. buf is set to a buffer of appropriate + * size, or NULL if -1 is returned. + * + */ +ssize_t slurp(const char *path, char **buf) { + FILE *f; + if ((f = fopen(path, "r")) == NULL) { + ELOG("Cannot open file \"%s\": %s\n", path, strerror(errno)); + return -1; + } + struct stat stbuf; + if (fstat(fileno(f), &stbuf) != 0) { + ELOG("Cannot fstat() \"%s\": %s\n", path, strerror(errno)); + fclose(f); + return -1; + } + /* Allocate one extra NUL byte to make the buffer usable with C string + * functions. yajl doesn’t need this, but this makes slurp safer. */ + *buf = scalloc(stbuf.st_size + 1, 1); + size_t n = fread(*buf, 1, stbuf.st_size, f); + fclose(f); + if ((ssize_t)n != stbuf.st_size) { + ELOG("File \"%s\" could not be read entirely: got %zd, want %zd\n", path, n, stbuf.st_size); + free(buf); + *buf = NULL; + return -1; + } + return (ssize_t)n; +} From b421799d9c4cd6f866963f9efc6a992d91e35221 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 14 Sep 2017 17:48:37 +0200 Subject: [PATCH 067/123] tests: unflake t/257-keypress-group1-fallback.t (#2946) fixes #2944 --- testcases/t/257-keypress-group1-fallback.t | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t index 212dfd15..843b8fe6 100644 --- a/testcases/t/257-keypress-group1-fallback.t +++ b/testcases/t/257-keypress-group1-fallback.t @@ -89,6 +89,9 @@ is(listen_for_binding( sync_with_i3; is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events'); +# Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases. +system(q|setxkbmap us -option|); + exit_gracefully($pid); } From 8e520deb898004303ff889f9636418201ba0dffd Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Thu, 14 Sep 2017 15:51:49 +0000 Subject: [PATCH 068/123] Fix typo in con_parent_with_orientation description --- include/con.h | 2 +- src/con.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/con.h b/include/con.h index 6a7e31bc..6cd1ef3e 100644 --- a/include/con.h +++ b/include/con.h @@ -102,7 +102,7 @@ Con *con_get_output(Con *con); Con *con_get_workspace(Con *con); /** - * Searches parenst of the given 'con' until it reaches one with the specified + * Searches parents of the given 'con' until it reaches one with the specified * 'orientation'. Aborts when it comes across a floating_con. * */ diff --git a/src/con.c b/src/con.c index 9797afa6..04aacd32 100644 --- a/src/con.c +++ b/src/con.c @@ -403,7 +403,7 @@ Con *con_get_workspace(Con *con) { } /* - * Searches parenst of the given 'con' until it reaches one with the specified + * Searches parents of the given 'con' until it reaches one with the specified * 'orientation'. Aborts when it comes across a floating_con. * */ From ba7a76e3675a2be5d3d483954c49a219d434ae19 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Thu, 14 Sep 2017 15:41:48 +0000 Subject: [PATCH 069/123] Fix erratic behavior with single container child jumping outputs This fixes a regression introduced in commit 4e88c10564ca5366c2578908f62ec56625a26718: when attempting to move the single child of a container in the direction of another output, i3 would move the window to the output, despite the window not being at the edge of its output, instead of moving it to its parent container. The bug occurred because the check for moving containers across outputs with non-default workspace layouts (issue #1603) did not actually verify that the moved window lies at the edge of the workspace, despite what its comment said. Fixes issue #2466. --- src/move.c | 3 +- testcases/t/537-move-single-to-output.t | 63 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 testcases/t/537-move-single-to-output.t diff --git a/src/move.c b/src/move.c index 70c8c788..3ecc69e4 100644 --- a/src/move.c +++ b/src/move.c @@ -248,7 +248,8 @@ void tree_move(Con *con, int direction) { ? AFTER : BEFORE); insert_con_into(con, target, position); - } else if (con->parent->parent->type == CT_WORKSPACE && + } else if (!next && + con->parent->parent->type == CT_WORKSPACE && con->parent->layout != L_DEFAULT && con_num_children(con->parent) == 1) { /* Con is the lone child of a non-default layout container at the edge diff --git a/testcases/t/537-move-single-to-output.t b/testcases/t/537-move-single-to-output.t new file mode 100644 index 00000000..db5b4925 --- /dev/null +++ b/testcases/t/537-move-single-to-output.t @@ -0,0 +1,63 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests that windows inside containers with a single child do not jump +# over other containers and erratically move across outputs. +# Ticket: #2466 +# Bug still in: 4.14-63-g75d11820 +use i3test i3_config => < Date: Sat, 16 Sep 2017 17:28:44 +0200 Subject: [PATCH 070/123] ipc: tree reply: document focus, nodes and floating_nodes (#2955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These haven’t ever changed, but were only included in the example, not in the list, so people might not have realized that these are safe for use. --- docs/ipc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/ipc b/docs/ipc index 65723577..ab2997fd 100644 --- a/docs/ipc +++ b/docs/ipc @@ -338,6 +338,16 @@ urgent (bool):: Whether this container (window or workspace) has the urgency hint set. focused (bool):: Whether this container is currently focused. +focus (array of integer):: + List of child node IDs (see +nodes+, +floating_nodes+ and +id+) in focus + order. Traversing the tree by following the first entry in this array + will result in eventually reaching the one node with +focused+ set to + true. +nodes (array of node):: + The tiling (i.e. non-floating) child containers of this node. +floating_nodes (array of node):: + The floating child containers of this node. Only non-empty on nodes with + type +workspace+. Please note that in the following example, I have left out some keys/values which are not relevant for the type of the node. Otherwise, the example would From 5dad79ff34735c85742cfdd34aa31d3039bec838 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 16 Sep 2017 23:53:31 +0300 Subject: [PATCH 071/123] Prevent freeing of uninitialized pointer > variable 'buf' is used uninitialized whenever 'if' condition is true Note: freeing a NULL pointer is fine. --- src/tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index b3d2ce93..7f466583 100644 --- a/src/tree.c +++ b/src/tree.c @@ -66,13 +66,13 @@ static Con *_create___i3(void) { bool tree_restore(const char *path, xcb_get_geometry_reply_t *geometry) { bool result = false; char *globbed = resolve_tilde(path); + char *buf = NULL; if (!path_exists(globbed)) { LOG("%s does not exist, not restoring tree\n", globbed); goto out; } - char *buf = NULL; ssize_t len; if ((len = slurp(globbed, &buf)) < 0) { /* slurp already logged an error. */ From b5583d6cf4e0426d9aedd589f18e03407793199b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 17 Sep 2017 00:24:15 +0300 Subject: [PATCH 072/123] Fix wrong call to free To confirm, assign n to a constant value and try to use the append_layout command. Without the change i3 crashes. --- src/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.c b/src/util.c index cd5ee03e..ba0969c7 100644 --- a/src/util.c +++ b/src/util.c @@ -501,7 +501,7 @@ ssize_t slurp(const char *path, char **buf) { fclose(f); if ((ssize_t)n != stbuf.st_size) { ELOG("File \"%s\" could not be read entirely: got %zd, want %zd\n", path, n, stbuf.st_size); - free(buf); + free(*buf); *buf = NULL; return -1; } From 395dc7bebd839581440fbb0b086aa1312009d71c Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 17 Sep 2017 01:14:47 +0300 Subject: [PATCH 073/123] Fix use of err after it is freed --- src/randr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index bc791696..88cad7c5 100644 --- a/src/randr.c +++ b/src/randr.c @@ -1048,8 +1048,8 @@ void randr_init(int *event_base, const bool disable_randr15) { xcb_randr_query_version_reply( conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err); if (err != NULL) { - free(err); ELOG("Could not query RandR version: X11 error code %d\n", err->error_code); + free(err); fallback_to_root_output(); return; } From 3bc91118c8d5200f4de1c531c47f847def851574 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 17 Sep 2017 15:25:00 +0200 Subject: [PATCH 074/123] ipc: rename COMMAND to RUN_COMMAND for consistency (#2956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All other message types are verbs, only our first-ever message COMMAND wasn’t. While we’re here, also change the message type dictionary into a table with clickable links to the corresponding reply type. Authors of downstream IPC libraries are encouraged to keep the old name around so as to not break existing code, but mark it as deprecated. --- AnyEvent-I3/lib/AnyEvent/I3.pm | 7 +++-- docs/ipc | 57 ++++++++++++++-------------------- i3-msg/main.c | 8 +++-- i3bar/src/xcb.c | 4 +-- include/i3/ipc.h | 5 ++- src/ipc.c | 4 +-- src/main.c | 6 ++-- 7 files changed, 44 insertions(+), 47 deletions(-) diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 75845ccd..134b1eb4 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -87,6 +87,7 @@ use base 'Exporter'; our @EXPORT = qw(i3); +use constant TYPE_RUN_COMMAND => 0; use constant TYPE_COMMAND => 0; use constant TYPE_GET_WORKSPACES => 1; use constant TYPE_SUBSCRIBE => 2; @@ -99,7 +100,7 @@ use constant TYPE_GET_BINDING_MODES => 8; use constant TYPE_GET_CONFIG => 9; our %EXPORT_TAGS = ( 'all' => [ - qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS + qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION TYPE_GET_BINDING_MODES TYPE_GET_CONFIG) ] ); @@ -321,7 +322,7 @@ Sends a message of the specified C to i3, possibly containing the data structure C (or C, encoded as utf8, if C is a scalar), if specified. - my $reply = $i3->message(TYPE_COMMAND, "reload")->recv; + my $reply = $i3->message(TYPE_RUN_COMMAND, "reload")->recv; if ($reply->{success}) { say "Configuration successfully reloaded"; } @@ -531,7 +532,7 @@ sub command { $self->_ensure_connection; - $self->message(TYPE_COMMAND, $content) + $self->message(TYPE_RUN_COMMAND, $content) } =head1 AUTHOR diff --git a/docs/ipc b/docs/ipc index ab2997fd..e0ddbf79 100644 --- a/docs/ipc +++ b/docs/ipc @@ -50,38 +50,20 @@ The magic string currently is "i3-ipc" and will only be changed when a change in the IPC API is done which breaks compatibility (we hope that we don’t need to do that). -Currently implemented message types are the following: - -COMMAND (0):: - The payload of the message is a command for i3 (like the commands you - can bind to keys in the configuration file) and will be executed - directly after receiving it. -GET_WORKSPACES (1):: - Gets the current workspaces. The reply will be a JSON-encoded list of - workspaces (see the reply section). -SUBSCRIBE (2):: - Subscribes your connection to certain events. See <> for a - description of this message and the concept of events. -GET_OUTPUTS (3):: - Gets the current outputs. The reply will be a JSON-encoded list of outputs - (see the reply section). -GET_TREE (4):: - Gets the layout tree. i3 uses a tree as data structure which includes - every container. The reply will be the JSON-encoded tree (see the reply - section). -GET_MARKS (5):: - Gets a list of marks (identifiers for containers to easily jump to them - later). The reply will be a JSON-encoded list of window marks (see - reply section). -GET_BAR_CONFIG (6):: - Gets the configuration (as JSON map) of the workspace bar with the - given ID. If no ID is provided, an array with all configured bar IDs is - returned instead. -GET_VERSION (7):: - Gets the version of i3. The reply will be a JSON-encoded dictionary - with the major, minor, patch and human-readable version. -GET_BINDING_MODES (8):: - Gets a list of currently configured binding modes. +.Currently implemented message types +[options="header",cols="^10%,^20%,^20%,^50%"] +|====================================================== +| Type (numeric) | Type (name) | Reply type | Purpose +| 0 | +RUN_COMMAND+ | <<_command_reply,COMMAND>> | Run the payload as an i3 command (like the commands you can bind to keys). +| 1 | +GET_WORKSPACES+ | <<_workspaces_reply,WORKSPACES>> | Get the list of current workspaces. +| 2 | +SUBSCRIBE+ | <<_subscribe_reply,SUBSCRIBE>> | Subscribe this IPC connection to the event types specified in the message payload. See <>. +| 3 | +GET_OUTPUTS+ | <<_outputs_reply,OUTPUTS>> | Get the list of current outputs. +| 4 | +GET_TREE+ | <<_tree_reply,TREE>> | Get the i3 layout tree. +| 5 | +GET_MARKS+ | <<_marks_reply,MARKS>> | Gets the names of all currently set marks. +| 6 | +GET_BAR_CONFIG+ | <<_bar_config_reply,BAR_CONFIG>> | Gets the specified bar configuration or the names of all bar configurations if payload is empty. +| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version. +| 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes. +|====================================================== So, a typical message could look like this: -------------------------------------------------- @@ -124,7 +106,7 @@ payload. The following reply types are implemented: COMMAND (0):: - Confirmation/Error code for the COMMAND message. + Confirmation/Error code for the RUN_COMMAND message. WORKSPACES (1):: Reply to the GET_WORKSPACES message. SUBSCRIBE (2):: @@ -142,6 +124,7 @@ VERSION (7):: BINDING_MODES (8):: Reply to the GET_BINDING_MODES message. +[[_command_reply]] === COMMAND reply The reply consists of a list of serialized maps for each command that was @@ -153,6 +136,7 @@ human-readable error message in the property +error (string)+. [{ "success": true }] ------------------- +[[_workspaces_reply]] === WORKSPACES reply The reply consists of a serialized list of workspaces. Each workspace has the @@ -212,6 +196,7 @@ output (string):: ] ------------------- +[[_subscribe_reply]] === SUBSCRIBE reply The reply consists of a single serialized map. The only property is @@ -223,6 +208,7 @@ default) or whether a JSON parse error occurred. { "success": true } ------------------- +[[_outputs_reply]] === OUTPUTS reply The reply consists of a serialized list of outputs. Each output has the @@ -269,6 +255,7 @@ rect (map):: ] ------------------- +[[_tree_reply]] === TREE reply The reply consists of a serialized tree. Each node in the tree (representing @@ -481,6 +468,7 @@ JSON dump: } ------------------------ +[[_marks_reply]] === MARKS reply The reply consists of a single array of strings for each container that has a @@ -489,6 +477,7 @@ The order of that array is undefined. If no window has a mark the response will be the empty array []. +[[_bar_config_reply]] === BAR_CONFIG reply This can be used by third-party workspace bars (especially i3bar, but others @@ -588,6 +577,7 @@ binding_mode_text/binding_mode_bg/binding_mode_border:: } -------------- +[[_version_reply]] === VERSION reply The reply consists of a single JSON dictionary with the following keys: @@ -620,6 +610,7 @@ loaded_config_file_name (string):: } ------------------- +[[_binding_modes_reply]] === BINDING_MODES reply The reply consists of an array of all currently configured binding modes. diff --git a/i3-msg/main.c b/i3-msg/main.c index 1a172789..8907a6f7 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -167,7 +167,7 @@ int main(int argc, char *argv[]) { else socket_path = NULL; int o, option_index = 0; - uint32_t message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND; char *payload = NULL; bool quiet = false; @@ -188,7 +188,9 @@ int main(int argc, char *argv[]) { socket_path = sstrdup(optarg); } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) { - message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND; + } else if (strcasecmp(optarg, "run_command") == 0) { + message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND; } else if (strcasecmp(optarg, "get_workspaces") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; } else if (strcasecmp(optarg, "get_outputs") == 0) { @@ -207,7 +209,7 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG; } else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n"); + printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 2ba446b1..462184cb 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -522,7 +522,7 @@ void handle_button(xcb_button_press_event_t *event) { if (binding->input_code != event->detail) continue; - i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, binding->command); + i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command); return; } @@ -603,7 +603,7 @@ void handle_button(xcb_button_press_event_t *event) { buffer[outpos] = utf8_name[inpos]; } buffer[outpos] = '"'; - i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer); + i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer); free(buffer); } diff --git a/include/i3/ipc.h b/include/i3/ipc.h index e3891454..993a2a24 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -27,9 +27,12 @@ typedef struct i3_ipc_header { /** Never change this, only on major IPC breakage (don’t do that) */ #define I3_IPC_MAGIC "i3-ipc" -/** The payload of the message will be interpreted as a command */ +/** Deprecated: use I3_IPC_MESSAGE_TYPE_RUN_COMMAND */ #define I3_IPC_MESSAGE_TYPE_COMMAND 0 +/** The payload of the message will be interpreted as a command */ +#define I3_IPC_MESSAGE_TYPE_RUN_COMMAND 0 + /** Requests the current workspaces from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 diff --git a/src/ipc.c b/src/ipc.c index 274f6010..dc953adc 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -113,7 +113,7 @@ void ipc_shutdown(shutdown_reason_t reason) { * or not (at the moment, always returns true). * */ -IPC_HANDLER(command) { +IPC_HANDLER(run_command) { /* To get a properly terminated buffer, we copy * message_size bytes out of the buffer */ char *command = scalloc(message_size + 1, 1); @@ -1121,7 +1121,7 @@ IPC_HANDLER(get_config) { /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ handler_t handlers[10] = { - handle_command, + handle_run_command, handle_get_workspaces, handle_subscribe, handle_get_outputs, diff --git a/src/main.c b/src/main.c index 44e4517e..f651ad6e 100644 --- a/src/main.c +++ b/src/main.c @@ -418,7 +418,7 @@ int main(int argc, char *argv[]) { if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); - if (ipc_send_message(sockfd, strlen(payload), I3_IPC_MESSAGE_TYPE_COMMAND, + if (ipc_send_message(sockfd, strlen(payload), I3_IPC_MESSAGE_TYPE_RUN_COMMAND, (uint8_t *)payload) == -1) err(EXIT_FAILURE, "IPC: write()"); FREE(payload); @@ -432,8 +432,8 @@ int main(int argc, char *argv[]) { err(EXIT_FAILURE, "IPC: read()"); return 1; } - if (reply_type != I3_IPC_MESSAGE_TYPE_COMMAND) - errx(EXIT_FAILURE, "IPC: received reply of type %d but expected %d (COMMAND)", reply_type, I3_IPC_MESSAGE_TYPE_COMMAND); + if (reply_type != I3_IPC_REPLY_TYPE_COMMAND) + errx(EXIT_FAILURE, "IPC: received reply of type %d but expected %d (COMMAND)", reply_type, I3_IPC_REPLY_TYPE_COMMAND); printf("%.*s\n", reply_length, reply); FREE(reply); return 0; From 5acddc259b5f5060e69c3ef212581fc4837ef5a7 Mon Sep 17 00:00:00 2001 From: Kent Fredric Date: Mon, 18 Sep 2017 23:03:54 +1200 Subject: [PATCH 075/123] Migrate tooling to ExtUtils::MakeMaker (#2963) --- AnyEvent-I3/MANIFEST | 11 +--- AnyEvent-I3/MANIFEST.SKIP | 2 + AnyEvent-I3/Makefile.PL | 107 +++++++++++++++++++++++++++++++++----- 3 files changed, 98 insertions(+), 22 deletions(-) diff --git a/AnyEvent-I3/MANIFEST b/AnyEvent-I3/MANIFEST index 34c8a8fb..8a166929 100644 --- a/AnyEvent-I3/MANIFEST +++ b/AnyEvent-I3/MANIFEST @@ -1,17 +1,8 @@ Changes -inc/Module/Install.pm -inc/Module/Install/Base.pm -inc/Module/Install/Can.pm -inc/Module/Install/Fetch.pm -inc/Module/Install/Makefile.pm -inc/Module/Install/Metadata.pm -inc/Module/Install/Win32.pm -inc/Module/Install/WriteAll.pm lib/AnyEvent/I3.pm Makefile.PL -MANIFEST +MANIFEST This list of files MANIFEST.SKIP -META.yml README t/00-load.t t/01-workspaces.t diff --git a/AnyEvent-I3/MANIFEST.SKIP b/AnyEvent-I3/MANIFEST.SKIP index 01bee91f..18e9d9df 100644 --- a/AnyEvent-I3/MANIFEST.SKIP +++ b/AnyEvent-I3/MANIFEST.SKIP @@ -9,3 +9,5 @@ Build.bat \.tar\.gz$ ^pod2htm(.*).tmp$ ^AnyEvent-I3- +^MYMETA.* +^MANIFEST\.bak diff --git a/AnyEvent-I3/Makefile.PL b/AnyEvent-I3/Makefile.PL index 5d2ab32e..29a442f4 100644 --- a/AnyEvent-I3/Makefile.PL +++ b/AnyEvent-I3/Makefile.PL @@ -1,17 +1,100 @@ -use lib '.'; -use inc::Module::Install; +use strict; +use warnings; -name 'AnyEvent-I3'; -all_from 'lib/AnyEvent/I3.pm'; -author 'Michael Stapelberg'; +use 5.006; +use ExtUtils::MakeMaker; -requires 'AnyEvent'; -requires 'AnyEvent::Handle'; -requires 'AnyEvent::Socket'; -requires 'JSON::XS'; - -if ($^O eq 'MSWin32') { +if ( $^O eq 'MSWin32' ) { die "AnyEvent::I3 cannot be used on win32 (unix sockets are missing)"; } -WriteAll; +my %meta = ( + name => 'AnyEvent-I3', + author => 'Michael Stapelberg, C<< >>', + license => ['perl'], + 'meta-spec' => { version => 2 }, + resources => { + repository => { + url => 'git://github.com/i3/i3', + web => 'https://github.com/i3/i3', + type => 'git', + }, + bugtracker => { + web => 'https://github.com/i3/i3/issues', + }, + homepage => 'https://i3wm.org/', + license => ['http://dev.perl.org/licenses'], + }, +); + +my %requirements = ( + configure_requires => { + 'ExtUtils::MakeMaker' => 6.36, + }, + build_requires => { + 'ExtUtils::MakeMaker' => 6.36 + }, + runtime_requires => { + 'AnyEvent' => 0, + 'AnyEvent::Handle' => 0, + 'AnyEvent::Socket' => 0, + 'JSON::XS' => 0, + }, + test_requires => { + 'Test::More' => 0.80, + }, +); + +my %merged_requirements = ( + 'ExtUtils::MakeMaker' => 0, + 'AnyEvent' => 0, + 'AnyEvent::Handle' => 0, + 'AnyEvent::Socket' => 0, + 'JSON::XS' => 0, + 'Test::More' => 0.80, +); + +$meta{prereqs}{configure}{requires} = $requirements{configure_requires}; +$meta{prereqs}{build}{requires} = $requirements{build_requires}; +$meta{prereqs}{runtime}{requires} = $requirements{runtime_requires}; +$meta{prereqs}{test}{requires} = $requirements{test_requires}; + +my %MM_Args = ( + AUTHOR => 'Michael Stapelberg', + NAME => 'AnyEvent::I3', + DISTNAME => 'AnyEvent-I3', + EXE_FILES => [], + MIN_PERL_VERSION => '5.006', + VERSION_FROM => 'lib/AnyEvent/I3.pm', + ABSTRACT_FROM => 'lib/AnyEvent/I3.pm', + test => { + TESTS => 't/*.t', + }, +); + +sub is_eumm { + eval { ExtUtils::MakeMaker->VERSION( $_[0] ) }; +} + +is_eumm(6.30) and $MM_Args{LICENSE} = $meta{license}[0]; +is_eumm(6.47_01) or delete $MM_Args{MIN_PERL_VERSION}; +is_eumm(6.52) + and $MM_Args{CONFIGURE_REQUIRES} = $requirements{configure_requires}; + +is_eumm(6.57_02) and !is_eumm(6.57_07) and $MM_Args{NO_MYMETA} = 1; + +if ( is_eumm(6.63_03) ) { + %MM_Args = ( + %MM_Args, + TEST_REQUIRES => $requirements{test_requires}, + BUILD_REQUIRES => $requirements{build_requires}, + PREREQ_PM => $requirements{runtime_requires}, + ); +} +else { + $MM_Args{PREREQ_PM} = \%merged_requirements; +} +unless ( -f 'META.yml' ) { + $MM_Args{META_ADD} = \%meta; +} +WriteMakefile(%MM_Args); From 910bcd68bc182e83082678222e68bfa3cd562ff0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Sep 2017 16:36:34 +0200 Subject: [PATCH 076/123] docs/ipc: "urgent": complete the list of container types (#2967) Thanks chressie! --- docs/ipc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/ipc b/docs/ipc index e0ddbf79..75ea9179 100644 --- a/docs/ipc +++ b/docs/ipc @@ -322,7 +322,10 @@ window (integer):: containers. This ID corresponds to what xwininfo(1) and other X11-related tools display (usually in hex). urgent (bool):: - Whether this container (window or workspace) has the urgency hint set. + Whether this container (window, split container, floating container or + workspace) has the urgency hint set, directly or indirectly. All parent + containers up until the workspace container will be marked urgent if they + have at least one urgent child. focused (bool):: Whether this container is currently focused. focus (array of integer):: From 0b6d851d7ca1ec00aa957232807cc7fb656e1daa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Sep 2017 16:36:57 +0200 Subject: [PATCH 077/123] =?UTF-8?q?Bugfix:=20don=E2=80=99t=20invalidate=20?= =?UTF-8?q?layout=20upon=20invalid=20'layout=20toggle'=20params=20(#2965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #2903 --- src/con.c | 4 +++- testcases/t/292-regress-layout-toggle.t | 29 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 testcases/t/292-regress-layout-toggle.t diff --git a/src/con.c b/src/con.c index 04aacd32..df115230 100644 --- a/src/con.c +++ b/src/con.c @@ -1841,7 +1841,9 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { } } - con_set_layout(con, new_layout); + if (new_layout != L_DEFAULT) { + con_set_layout(con, new_layout); + } } else if (strcasecmp(toggle_mode, "all") == 0 || strcasecmp(toggle_mode, "default") == 0) { if (parent->layout == L_STACKED) con_set_layout(con, L_TABBED); diff --git a/testcases/t/292-regress-layout-toggle.t b/testcases/t/292-regress-layout-toggle.t new file mode 100644 index 00000000..a8fd3a6d --- /dev/null +++ b/testcases/t/292-regress-layout-toggle.t @@ -0,0 +1,29 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Regression test: verify layout toggle with invalid parameters does not set +# layout to L_DEFAULT, which crashes i3 upon the next IPC message. +# Ticket: #2903 +# Bug still in: 4.14-87-g607e97e6 +use i3test; + +cmd 'layout toggle 1337 1337'; + +fresh_workspace; + +does_i3_live; + +done_testing; From 7726b9a759f24e70c8d2cffce3c582930db93dc1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Sep 2017 16:37:34 +0200 Subject: [PATCH 078/123] Bugfix: avert crash by fixing focus when creating output containers (#2966) fixes #2854 --- src/randr.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/randr.c b/src/randr.c index 88cad7c5..19c40577 100644 --- a/src/randr.c +++ b/src/randr.c @@ -380,6 +380,10 @@ void output_init_con(Output *output) { FREE(name); DLOG("attaching\n"); con_attach(bottomdock, con, false); + + /* Change focus to the content container */ + TAILQ_REMOVE(&(con->focus_head), content, focused); + TAILQ_INSERT_HEAD(&(con->focus_head), content, focused); } /* From 501dfd6eb8b0db1cd8d77f0069e41c9dbaa80e3b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Sep 2017 17:15:28 +0200 Subject: [PATCH 079/123] =?UTF-8?q?ipc:=20document=20how=20to=20detect=20i?= =?UTF-8?q?3=E2=80=99s=20byte=20order=20in=20memory-safe=20languages=20(#2?= =?UTF-8?q?961)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit related to issue #2958 --- docs/ipc | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/ipc b/docs/ipc index 75ea9179..4c2c778a 100644 --- a/docs/ipc +++ b/docs/ipc @@ -883,3 +883,63 @@ Rust:: * https://github.com/tmerr/i3ipc-rs OCaml:: * https://github.com/Armael/ocaml-i3ipc + +== Appendix A: Detecting byte order in memory-safe languages + +Some programming languages such as Go don’t offer a way to serialize data in the +native byte order of the machine they’re running on without resorting to tricks +involving the +unsafe+ package. + +The following technique can be used (and will not be broken by changes to i3) to +detect the byte order i3 is using: + +1. The byte order dependent fields of an IPC message are message type and + payload length. + + * The message type +RUN_COMMAND+ (0) is the same in big and little endian, so + we can use it in either byte order to elicit a reply from i3. + + * The payload length 65536 + 256 (+0x00 01 01 00+) is the same in big and + little endian, and also small enough to not worry about memory allocations + of that size. We must use payloads of length 65536 + 256 in every message + we send, so that i3 will be able to read the entire message regardless of + the byte order it uses. + +2. Send a big endian encoded message of type +SUBSCRIBE+ (2) with payload `[]` + followed by 65536 + 256 - 2 +SPACE+ (ASCII 0x20) bytes. + + * If i3 is running in big endian, this message is treated as a noop, + resulting in a +SUBSCRIBE+ reply with payload `{"success":true}` + footnote:[A small payload is important: that way, we circumvent dealing + with UNIX domain socket buffer sizes, whose size depends on the + implementation/operating system. Exhausting such a buffer results in an i3 + deadlock unless you concurrently read and write, which — depending on the + programming language — makes the technique much more complicated.]. + + * If i3 is running in little endian, this message is read in its entirety due + to the byte order independent payload length, then + https://github.com/i3/i3/blob/d726d09d496577d1c337a4b97486f2c9fbc914f1/src/ipc.c#L1188[silently + discarded] due to the unknown message type. + +3. Send a byte order independent message, i.e. type +RUN_COMMAND+ (0) with + payload +nop byte order detection. padding:+, padded to 65536 + 256 bytes + with +a+ (ASCII 0x61) bytes. i3 will reply to this message with a reply of + type +COMMAND+ (0). + + * The human-readable prefix is in there to not confuse readers of the i3 log. + + * This messages serves as a synchronization primitive so that we know whether + i3 discarded the +SUBSCRIBE+ message or didn’t answer it yet. + +4. Receive a message header from i3, decoding the message type as big endian. + + * If the message’s reply type is +COMMAND+ (0), i3 is running in little + endian (because the +SUBSCRIBE+ message was discarded). Decode the message + payload length as little endian, receive the message payload. + + * If the message’s reply type is anything else, i3 is running in big endian + (because our big endian encoded +SUBSCRIBE+ message was answered). Decode + the message payload length in big endian, receive the message + payload. Then, receive the pending +COMMAND+ message reply in big endian. + +5. From here on out, send/receive all messages using the detected byte order. From ee1546386b55b34c557dd6e3ba537aea7f1b8d63 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Sep 2017 17:50:23 +0200 Subject: [PATCH 080/123] =?UTF-8?q?t/265-swap:=20don=E2=80=99t=20start=20n?= =?UTF-8?q?ew=20i3=20instances=20with=20the=20same=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $config is never touched after being initially set up. Not restarting i3 between each test case reduces the runtime of this test by an order of magnitude. --- testcases/t/265-swap.t | 47 ++++-------------------------------------- 1 file changed, 4 insertions(+), 43 deletions(-) diff --git a/testcases/t/265-swap.t b/testcases/t/265-swap.t index e3c8e97c..9d40af9f 100644 --- a/testcases/t/265-swap.t +++ b/testcases/t/265-swap.t @@ -32,26 +32,24 @@ my ($nodes, $expected_focus, $A, $B, $F); my ($result); my @urgent; +$pid = launch_with_config($config); + ############################################################################### # Invalid con_id should not crash i3 # See issue #2895. ############################################################################### -$pid = launch_with_config($config); $ws = fresh_workspace; open_window; cmd "swap container with con_id 1"; does_i3_live; -exit_gracefully($pid); ############################################################################### # Swap 2 windows in different workspaces using con_id ############################################################################### -$pid = launch_with_config($config); - $ws = fresh_workspace; open_window; $A = get_focused($ws); @@ -62,8 +60,6 @@ open_window; cmd "swap container with con_id $A"; is(get_focused($ws), $A, 'A is now focused'); -exit_gracefully($pid); - ############################################################################### # Swap two containers next to each other. # Focus should stay on B because both windows are on the focused workspace. @@ -73,7 +69,6 @@ exit_gracefully($pid); # | A | B | Focus Stacks: # +---+---+ H1: B, A ############################################################################### -$pid = launch_with_config($config); $ws = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -87,8 +82,6 @@ is($nodes->[0]->{window}, $B->{id}, 'B is on the left'); is($nodes->[1]->{window}, $A->{id}, 'A is on the right'); is(get_focused($ws), $expected_focus, 'B is still focused'); -exit_gracefully($pid); - ############################################################################### # Swap two containers with different parents. # In this test, the focus head of the left v-split container is A. @@ -100,7 +93,6 @@ exit_gracefully($pid); # | Y | B | V1: A, Y # +---+---+ V2: B, X ############################################################################### -$pid = launch_with_config($config); $ws = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -120,8 +112,6 @@ is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); is(get_focused($ws), $expected_focus, 'B is still focused'); -exit_gracefully($pid); - ############################################################################### # Swap two containers with different parents. # In this test, the focus head of the left v-split container is _not_ A. @@ -133,7 +123,6 @@ exit_gracefully($pid); # | Y | B | V1: Y, A # +---+---+ V2: B, X ############################################################################### -$pid = launch_with_config($config); $ws = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -153,8 +142,6 @@ is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); is(get_focused($ws), $expected_focus, 'B is still focused'); -exit_gracefully($pid); - ############################################################################### # Swap two containers with one being on a different workspace. # The focused container is B. @@ -171,8 +158,6 @@ exit_gracefully($pid); # | Y | B | Focus Stacks: # +---+---+ H2: B, Y ############################################################################### -$pid = launch_with_config($config); - $ws1 = fresh_workspace; $A = open_window(wm_class => 'mark_A'); $expected_focus = get_focused($ws1); @@ -192,8 +177,6 @@ $nodes = get_ws_content($ws2); is($nodes->[1]->{window}, $A->{id}, 'A is on ws1:right'); is(get_focused($ws2), $expected_focus, 'A is focused'); -exit_gracefully($pid); - ############################################################################### # Swap two non-focused containers within the same workspace. # @@ -203,7 +186,6 @@ exit_gracefully($pid); # | X | B | V1: A, X # +---+---+ V2: F, B ############################################################################### -$pid = launch_with_config($config); $ws = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -223,8 +205,6 @@ is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); is(get_focused($ws), $expected_focus, 'F is still focused'); -exit_gracefully($pid); - ############################################################################### # Swap two non-focused containers which are both on different workspaces. # @@ -244,8 +224,6 @@ exit_gracefully($pid); # | F | # +---+ ############################################################################### -$pid = launch_with_config($config); - $ws1 = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -266,8 +244,6 @@ is($nodes->[0]->{window}, $A->{id}, 'A is on the second workspace'); is(get_focused($ws3), $expected_focus, 'F is still focused'); -exit_gracefully($pid); - ############################################################################### # Swap two non-focused containers with one being on a different workspace. # @@ -283,7 +259,6 @@ exit_gracefully($pid); # | B | F | Focus Stacks: # +---+---+ H2: F, B ############################################################################### -$pid = launch_with_config($config); $ws1 = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -302,8 +277,6 @@ $nodes = get_ws_content($ws2); is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace'); is(get_focused($ws2), $expected_focus, 'F is still focused'); -exit_gracefully($pid); - ############################################################################### # 1. A container cannot be swapped with its parent. # 2. A container cannot be swapped with one of its children. @@ -315,8 +288,6 @@ exit_gracefully($pid); # | | B | # +---+---+ ############################################################################### -$pid = launch_with_config($config); - $ws = fresh_workspace; open_window; open_window; @@ -330,8 +301,6 @@ is($result->[0]->{success}, 0, 'B cannot be swappd with its parent'); $result = cmd '[con_mark=A] swap container with mark B'; is($result->[0]->{success}, 0, 'A cannot be swappd with one of its children'); -exit_gracefully($pid); - ############################################################################### # Swapping two containers preserves the geometry of the container they are # being swapped with. @@ -346,8 +315,6 @@ exit_gracefully($pid); # | B | A | # +---+-------+ ############################################################################### -$pid = launch_with_config($config); - $ws = fresh_workspace; $A = open_window(wm_class => 'mark_A'); $B = open_window(wm_class => 'mark_B'); @@ -364,8 +331,6 @@ $nodes = get_ws_content($ws); cmp_float($nodes->[0]->{percent}, 0.25, 'B has 25% width'); cmp_float($nodes->[1]->{percent}, 0.75, 'A has 75% width'); -exit_gracefully($pid); - ############################################################################### # Swapping containers not sharing the same parent preserves the geometry of # the container they are swapped with. @@ -388,7 +353,6 @@ exit_gracefully($pid); # | | X | # +---+-----+ ############################################################################### -$pid = launch_with_config($config); $ws = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -411,12 +375,9 @@ $nodes = get_ws_content($ws); cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'B has 25% height'); cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'A has 75% height'); -exit_gracefully($pid); - ############################################################################### # Swapping containers moves the urgency hint correctly. ############################################################################### -$pid = launch_with_config($config); $ws1 = fresh_workspace; $A = open_window(wm_class => 'mark_A'); @@ -437,8 +398,8 @@ is(get_ws($ws1)->{urgent}, 1, 'the first workspace is marked urgent'); is(@urgent, 0, 'A is not marked urgent'); is(get_ws($ws2)->{urgent}, 0, 'the second workspace is not marked urgent'); -exit_gracefully($pid); - ############################################################################### +exit_gracefully($pid); + done_testing; From e46dc19f823eb1f9d0a983b12b42e25358e9f250 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Sep 2017 18:21:59 +0200 Subject: [PATCH 081/123] 165-for_window: merge config and re-use i3 instance, split remainder This reduces total test wall-clock time by 1.5s (from 7.5s down to 5.9s). --- testcases/t/165-for_window.t | 237 +++++--------------- testcases/t/165-for_window_tilingfloating.t | 49 ++++ 2 files changed, 109 insertions(+), 177 deletions(-) create mode 100644 testcases/t/165-for_window_tilingfloating.t diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t index bc3df114..03eff9ae 100644 --- a/testcases/t/165-for_window.t +++ b/testcases/t/165-for_window.t @@ -19,6 +19,61 @@ use X11::XCB qw(PROP_MODE_REPLACE); my (@nodes); +my $config = <<'EOT'; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +# test 1, test 2 +for_window [class="borderless$"] border none +for_window [title="special borderless title"] border none + +# test 3 +for_window [class="borderless3$" title="usethis"] border none +for_window [class="borderless3$"] border none +for_window [title="special borderless title"] border none +for_window [title="special mark title"] border none, mark bleh + +# test 4 +for_window [class="borderless4$" title="usethis"] border none + +# test 5, test 6 +for_window [class="foo$"] border 1pixel + +# test 6 +for_window [instance="foo6"] border none + +# test 7 +for_window [id="asdf"] border none + +# test 8, test 9 +for_window [window_role="i3test"] border none + +# test 12 +for_window [workspace="trigger"] floating enable, mark triggered +EOT + +# test all window types +my %window_types = ( + 'normal' => '_NET_WM_WINDOW_TYPE_NORMAL', + 'dialog' => '_NET_WM_WINDOW_TYPE_DIALOG', + 'utility' => '_NET_WM_WINDOW_TYPE_UTILITY', + 'toolbar' => '_NET_WM_WINDOW_TYPE_TOOLBAR', + 'splash' => '_NET_WM_WINDOW_TYPE_SPLASH', + 'menu' => '_NET_WM_WINDOW_TYPE_MENU', + 'dropdown_menu' => '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU', + 'popup_menu' => '_NET_WM_WINDOW_TYPE_POPUP_MENU', + 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP', + 'notification' => '_NET_WM_WINDOW_TYPE_NOTIFICATION' +); + +for my $window_type (keys %window_types) { + $config .= < 'Border window'); @@ -66,22 +112,11 @@ wait_for_unmap $window; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); -exit_gracefully($pid); - ############################################################## # 2: match on the title, check if for_window is really executed # only once ############################################################## -$config = < 'special title'); @@ -116,23 +151,10 @@ wait_for_unmap $window; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); -exit_gracefully($pid); - ############################################################## # 3: match on the title, set border style *and* a mark ############################################################## -$config = < 'special mark title'); @@ -154,25 +176,15 @@ cmd qq|[con_mark="bleh"] focus|; @content = @{get_ws_content($tmp)}; ok($content[0]->{focused}, 'first node focused'); -exit_gracefully($pid); - ############################################################## # 4: multiple criteria for the for_window command ############################################################## -$config = < 'usethis', - wm_class => 'borderless', + wm_class => 'borderless4', ); @content = @{get_ws_content($tmp)}; @@ -190,7 +202,7 @@ sync_with_i3; cmp_ok(@content, '==', 0, 'no nodes on this workspace now'); $window->_create; -$window->wm_class('borderless'); +$window->wm_class('borderless4'); $window->name('notthis'); $window->map; wait_for_map $window; @@ -199,24 +211,12 @@ wait_for_map $window; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'no border'); - -exit_gracefully($pid); - ############################################################## # 5: check that a class criterion does not match the instance ############################################################## -$config = < 'usethis', wm_class => 'bar', @@ -227,50 +227,26 @@ $window = open_window( cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border, not matched'); -exit_gracefully($pid); - ############################################################## # 6: check that the 'instance' criterion works ############################################################## -$config = < 'usethis', wm_class => 'bar', - instance => 'foo', + instance => 'foo6', ); @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); -exit_gracefully($pid); - ############################################################## # 7: check that invalid criteria don’t end up matching all windows ############################################################## -# this configuration is broken because "asdf" is not a valid integer -# the for_window should therefore recognize this error and don’t add the -# assignment -$config = <{border}, 'normal', 'normal border'); -exit_gracefully($pid); - ############################################################## # 8: check that the role criterion works properly ############################################################## -$config = <{border}, 'none', 'no border (window_role)'); -exit_gracefully($pid); - ############################################################## # 9: another test for the window_role, but this time it changes # *after* the window has been mapped ############################################################## -$config = < 'usethis'); @@ -364,45 +320,18 @@ sync_with_i3; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border (window_role 2)'); -exit_gracefully($pid); - ############################################################## # 10: check that the criterion 'window_type' works ############################################################## -# test all window types -my %window_types = ( - 'normal' => '_NET_WM_WINDOW_TYPE_NORMAL', - 'dialog' => '_NET_WM_WINDOW_TYPE_DIALOG', - 'utility' => '_NET_WM_WINDOW_TYPE_UTILITY', - 'toolbar' => '_NET_WM_WINDOW_TYPE_TOOLBAR', - 'splash' => '_NET_WM_WINDOW_TYPE_SPLASH', - 'menu' => '_NET_WM_WINDOW_TYPE_MENU', - 'dropdown_menu' => '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU', - 'popup_menu' => '_NET_WM_WINDOW_TYPE_POPUP_MENU', - 'tooltip' => '_NET_WM_WINDOW_TYPE_TOOLTIP', - 'notification' => '_NET_WM_WINDOW_TYPE_NOTIFICATION' -); - while (my ($window_type, $atom) = each %window_types) { - - $config = <<"EOT"; -# i3 config file (v4) -font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 -for_window [window_type="$window_type"] floating enable, mark branded -EOT - - $pid = launch_with_config($config); $tmp = fresh_workspace; $window = open_window(window_type => $x->atom(name => $atom)); my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); - is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)"); - - exit_gracefully($pid); - + is_deeply($nodes[0]->{nodes}[0]->{marks}, [ "branded-$window_type" ], "mark set (window_type = $atom)"); } ############################################################## @@ -411,14 +340,6 @@ EOT ############################################################## while (my ($window_type, $atom) = each %window_types) { - - $config = <<"EOT"; -# i3 config file (v4) -font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 -for_window [window_type="$window_type"] floating enable, mark branded -EOT - - $pid = launch_with_config($config); $tmp = fresh_workspace; $window = open_window(); @@ -432,24 +353,13 @@ EOT my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); - is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'branded' ], "mark set (window_type = $atom)"); - - exit_gracefully($pid); - + is_deeply($nodes[0]->{nodes}[0]->{marks}, [ "branded-$window_type" ], "mark set (window_type = $atom)"); } ############################################################## # 12: check that the criterion 'workspace' works ############################################################## -$config = <<"EOT"; -# i3 config file (v4) -font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 -for_window [workspace="trigger"] floating enable, mark triggered -EOT - -$pid = launch_with_config($config); - cmd 'workspace trigger'; $window = open_window; @@ -457,35 +367,8 @@ $window = open_window; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'triggered' ], "mark set for workspace criterion"); -exit_gracefully($pid); - ############################################################## -# 13: check that the tiling / floating criteria work. -############################################################## - -$config = <<"EOT"; -# i3 config file (v4) -font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 -for_window [tiling] mark tiled -for_window [floating] mark floated -EOT - -$pid = launch_with_config($config); -$tmp = fresh_workspace; - -open_window; -open_floating_window; - -@nodes = @{get_ws($tmp)->{nodes}}; -cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); -is_deeply($nodes[0]->{marks}, [ 'tiled' ], "mark set for 'tiling' criterion"); - -@nodes = @{get_ws($tmp)->{floating_nodes}}; -cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); -is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floated' ], "mark set for 'floating' criterion"); exit_gracefully($pid); -############################################################## - done_testing; diff --git a/testcases/t/165-for_window_tilingfloating.t b/testcases/t/165-for_window_tilingfloating.t new file mode 100644 index 00000000..760ac53e --- /dev/null +++ b/testcases/t/165-for_window_tilingfloating.t @@ -0,0 +1,49 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +use i3test i3_autostart => 0; +use X11::XCB qw(PROP_MODE_REPLACE); + +############################################################## +# 13: check that the tiling / floating criteria work. +############################################################## + +my $config = <<"EOT"; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +for_window [tiling] mark tiled +for_window [floating] mark floated +EOT + +my $pid = launch_with_config($config); +my $tmp = fresh_workspace; + +open_window; +open_floating_window; + +my @nodes = @{get_ws($tmp)->{nodes}}; +cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); +is_deeply($nodes[0]->{marks}, [ 'tiled' ], "mark set for 'tiling' criterion"); + +@nodes = @{get_ws($tmp)->{floating_nodes}}; +cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); +is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floated' ], "mark set for 'floating' criterion"); + +exit_gracefully($pid); + +############################################################## + +done_testing; From dbd3ca749cdf704e237881f00ab599f6a8aefb74 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 Sep 2017 18:39:56 +0200 Subject: [PATCH 082/123] 529-net-wm-desktop: avoid timeout, avoid restarts, split This shaves off almost half a second of the wall-clock time (from 5.9s to 5.6s). --- testcases/t/529-net-wm-desktop.t | 86 ++++------------------------- testcases/t/529-net-wm-desktop_mm.t | 75 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 74 deletions(-) create mode 100644 testcases/t/529-net-wm-desktop_mm.t diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t index 8f2df735..42b488d9 100644 --- a/testcases/t/529-net-wm-desktop.t +++ b/testcases/t/529-net-wm-desktop.t @@ -66,8 +66,15 @@ sub open_window_with_net_wm_desktop { pack('L', $idx), ); }, + dont_map => 1, ); + # We don’t wait for MapNotify and instead sync with i3 so that we don’t need + # to encounter the full timeout of 4s when opening a window on a non-visible + # workspace. + $window->map; + sync_with_i3; + return $window; } @@ -80,9 +87,7 @@ sub kill_windows { ############################################################################### -my ($config, $config_mm, $pid, $con); - -$config = <{floating_nodes}}, 1, 'The window is floating'); ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); kill_windows; -exit_gracefully($pid); ############################################################################### # _NET_WM_DESKTOP is updated when the window is moved to another workspace # on the same output. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 0'; open_window; cmd 'workspace 1'; @@ -178,15 +160,12 @@ cmd 'move window to workspace 1'; is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); kill_windows; -exit_gracefully($pid); ############################################################################### # _NET_WM_DESKTOP is updated when the floating window is moved to another # workspace on the same output. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 0'; open_window; cmd 'workspace 1'; @@ -200,35 +179,11 @@ cmd 'move window to workspace 1'; is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); kill_windows; -exit_gracefully($pid); - -############################################################################### -# _NET_WM_DESKTOP is updated when the window is moved to another workspace -# on another output. -############################################################################### - -$pid = launch_with_config($config_mm); - -cmd 'workspace 0'; -open_window; -cmd 'workspace 10'; -open_window; -cmd 'workspace 0'; -$con = open_window; - -cmd 'move window to workspace 10'; - -is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); - -kill_windows; -exit_gracefully($pid); ############################################################################### # _NET_WM_DESKTOP is removed when the window is withdrawn. ############################################################################### -$pid = launch_with_config($config); - $con = open_window; is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set (sanity check)'); @@ -238,15 +193,12 @@ wait_for_unmap($con); is(get_net_wm_desktop($con), undef, '_NET_WM_DESKTOP is removed'); kill_windows; -exit_gracefully($pid); ############################################################################### # A _NET_WM_DESKTOP client message sent to the root window moves a window # to the correct workspace. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 0'; open_window; cmd 'workspace 1'; @@ -263,15 +215,12 @@ is_num_children('1', 2, 'The window is now on workspace 1'); is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); kill_windows; -exit_gracefully($pid); ############################################################################### # A _NET_WM_DESKTOP client message sent to the root window can make a window # sticky. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 0'; $con = open_window; @@ -282,15 +231,12 @@ is(@{get_ws('0')->{floating_nodes}}, 1, 'The window is floating'); ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); kill_windows; -exit_gracefully($pid); ############################################################################### # _NET_WM_DESKTOP is updated when a new workspace with a lower number is # opened and closed. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 1'; $con = open_window; is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); @@ -299,14 +245,11 @@ cmd 'workspace 0'; is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); kill_windows; -exit_gracefully($pid); ############################################################################### # _NET_WM_DESKTOP is updated when a window is made sticky by command. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 0'; $con = open_window; cmd 'floating enable'; @@ -316,14 +259,11 @@ cmd 'sticky enable'; is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); kill_windows; -exit_gracefully($pid); ############################################################################### # _NET_WM_DESKTOP is updated when a window is made sticky by client message. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 0'; $con = open_window; cmd 'floating enable'; @@ -343,14 +283,11 @@ sync_with_i3; is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); kill_windows; -exit_gracefully($pid); ############################################################################### # _NET_WM_DESKTOP is updated when a window is moved to the scratchpad. ############################################################################### -$pid = launch_with_config($config); - cmd 'workspace 0'; $con = open_window; cmd 'floating enable'; @@ -363,8 +300,9 @@ cmd 'scratchpad show'; is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); kill_windows; -exit_gracefully($pid); ############################################################################### +exit_gracefully($pid); + done_testing; diff --git a/testcases/t/529-net-wm-desktop_mm.t b/testcases/t/529-net-wm-desktop_mm.t new file mode 100644 index 00000000..77238946 --- /dev/null +++ b/testcases/t/529-net-wm-desktop_mm.t @@ -0,0 +1,75 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for _NET_WM_DESKTOP. +# Ticket: #2153 +use i3test i3_autostart => 0; +use X11::XCB qw(:all); + +sub get_net_wm_desktop { + sync_with_i3; + + my ($con) = @_; + my $cookie = $x->get_property( + 0, + $con->{id}, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 1 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + return undef if $reply->{length} != 1; + + return unpack("L", $reply->{value}); +} + +my $config = < Date: Sun, 10 Sep 2017 11:30:56 +0200 Subject: [PATCH 083/123] i3test: add kill_all_windows convenience function --- testcases/lib/i3test.pm.in | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index ca64edfd..5e3f8b2d 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -46,6 +46,7 @@ our @EXPORT = qw( wait_for_map wait_for_unmap $x + kill_all_windows ); =head1 NAME @@ -900,6 +901,17 @@ sub get_i3_log { return slurp($logfile); } +=head2 kill_all_windows + +Kills all windows to clean up between tests. + +=cut +sub kill_all_windows { + # Sync in case not all windows are managed by i3 just yet. + sync_with_i3; + cmd '[title=".*"] kill'; +} + =head1 AUTHOR Michael Stapelberg From 31834b3ce4c13f1d08a07f1e1c4d823760ea53da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 10 Sep 2017 11:31:10 +0200 Subject: [PATCH 084/123] Kill windows between tests --- testcases/t/165-for_window.t | 24 ++++++++++++++++++++++++ testcases/t/265-swap.t | 25 +++++++++++++++++++++++++ testcases/t/529-net-wm-desktop.t | 31 ++++++++++++------------------- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t index 03eff9ae..984cd6da 100644 --- a/testcases/t/165-for_window.t +++ b/testcases/t/165-for_window.t @@ -112,6 +112,8 @@ wait_for_unmap $window; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); +kill_all_windows; + ############################################################## # 2: match on the title, check if for_window is really executed # only once @@ -151,6 +153,8 @@ wait_for_unmap $window; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); +kill_all_windows; + ############################################################## # 3: match on the title, set border style *and* a mark ############################################################## @@ -176,6 +180,8 @@ cmd qq|[con_mark="bleh"] focus|; @content = @{get_ws_content($tmp)}; ok($content[0]->{focused}, 'first node focused'); +kill_all_windows; + ############################################################## # 4: multiple criteria for the for_window command ############################################################## @@ -211,6 +217,8 @@ wait_for_map $window; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'no border'); +kill_all_windows; + ############################################################## # 5: check that a class criterion does not match the instance ############################################################## @@ -227,6 +235,8 @@ $window = open_window( cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border, not matched'); +kill_all_windows; + ############################################################## # 6: check that the 'instance' criterion works ############################################################## @@ -243,6 +253,8 @@ $window = open_window( cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); +kill_all_windows; + ############################################################## # 7: check that invalid criteria don’t end up matching all windows ############################################################## @@ -259,6 +271,8 @@ $window = open_window( cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border'); +kill_all_windows; + ############################################################## # 8: check that the role criterion works properly ############################################################## @@ -287,6 +301,8 @@ $window = open_window( cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border (window_role)'); +kill_all_windows; + ############################################################## # 9: another test for the window_role, but this time it changes # *after* the window has been mapped @@ -320,6 +336,8 @@ sync_with_i3; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border (window_role 2)'); +kill_all_windows; + ############################################################## # 10: check that the criterion 'window_type' works ############################################################## @@ -332,6 +350,8 @@ while (my ($window_type, $atom) = each %window_types) { my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ "branded-$window_type" ], "mark set (window_type = $atom)"); + + kill_all_windows; } ############################################################## @@ -354,6 +374,8 @@ while (my ($window_type, $atom) = each %window_types) { my @nodes = @{get_ws($tmp)->{floating_nodes}}; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ "branded-$window_type" ], "mark set (window_type = $atom)"); + + kill_all_windows; } ############################################################## @@ -367,6 +389,8 @@ $window = open_window; cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'triggered' ], "mark set for workspace criterion"); +kill_all_windows; + ############################################################## exit_gracefully($pid); diff --git a/testcases/t/265-swap.t b/testcases/t/265-swap.t index 9d40af9f..3ba8f51b 100644 --- a/testcases/t/265-swap.t +++ b/testcases/t/265-swap.t @@ -45,6 +45,7 @@ open_window; cmd "swap container with con_id 1"; does_i3_live; +kill_all_windows; ############################################################################### # Swap 2 windows in different workspaces using con_id @@ -60,6 +61,8 @@ open_window; cmd "swap container with con_id $A"; is(get_focused($ws), $A, 'A is now focused'); +kill_all_windows; + ############################################################################### # Swap two containers next to each other. # Focus should stay on B because both windows are on the focused workspace. @@ -82,6 +85,8 @@ is($nodes->[0]->{window}, $B->{id}, 'B is on the left'); is($nodes->[1]->{window}, $A->{id}, 'A is on the right'); is(get_focused($ws), $expected_focus, 'B is still focused'); +kill_all_windows; + ############################################################################### # Swap two containers with different parents. # In this test, the focus head of the left v-split container is A. @@ -112,6 +117,8 @@ is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); is(get_focused($ws), $expected_focus, 'B is still focused'); +kill_all_windows; + ############################################################################### # Swap two containers with different parents. # In this test, the focus head of the left v-split container is _not_ A. @@ -142,6 +149,8 @@ is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); is(get_focused($ws), $expected_focus, 'B is still focused'); +kill_all_windows; + ############################################################################### # Swap two containers with one being on a different workspace. # The focused container is B. @@ -177,6 +186,8 @@ $nodes = get_ws_content($ws2); is($nodes->[1]->{window}, $A->{id}, 'A is on ws1:right'); is(get_focused($ws2), $expected_focus, 'A is focused'); +kill_all_windows; + ############################################################################### # Swap two non-focused containers within the same workspace. # @@ -205,6 +216,8 @@ is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); is(get_focused($ws), $expected_focus, 'F is still focused'); +kill_all_windows; + ############################################################################### # Swap two non-focused containers which are both on different workspaces. # @@ -244,6 +257,8 @@ is($nodes->[0]->{window}, $A->{id}, 'A is on the second workspace'); is(get_focused($ws3), $expected_focus, 'F is still focused'); +kill_all_windows; + ############################################################################### # Swap two non-focused containers with one being on a different workspace. # @@ -277,6 +292,8 @@ $nodes = get_ws_content($ws2); is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace'); is(get_focused($ws2), $expected_focus, 'F is still focused'); +kill_all_windows; + ############################################################################### # 1. A container cannot be swapped with its parent. # 2. A container cannot be swapped with one of its children. @@ -301,6 +318,8 @@ is($result->[0]->{success}, 0, 'B cannot be swappd with its parent'); $result = cmd '[con_mark=A] swap container with mark B'; is($result->[0]->{success}, 0, 'A cannot be swappd with one of its children'); +kill_all_windows; + ############################################################################### # Swapping two containers preserves the geometry of the container they are # being swapped with. @@ -331,6 +350,8 @@ $nodes = get_ws_content($ws); cmp_float($nodes->[0]->{percent}, 0.25, 'B has 25% width'); cmp_float($nodes->[1]->{percent}, 0.75, 'A has 75% width'); +kill_all_windows; + ############################################################################### # Swapping containers not sharing the same parent preserves the geometry of # the container they are swapped with. @@ -375,6 +396,8 @@ $nodes = get_ws_content($ws); cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'B has 25% height'); cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'A has 75% height'); +kill_all_windows; + ############################################################################### # Swapping containers moves the urgency hint correctly. ############################################################################### @@ -398,6 +421,8 @@ is(get_ws($ws1)->{urgent}, 1, 'the first workspace is marked urgent'); is(@urgent, 0, 'A is not marked urgent'); is(get_ws($ws2)->{urgent}, 0, 'the second workspace is not marked urgent'); +kill_all_windows; + ############################################################################### exit_gracefully($pid); diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t index 42b488d9..5f858051 100644 --- a/testcases/t/529-net-wm-desktop.t +++ b/testcases/t/529-net-wm-desktop.t @@ -78,13 +78,6 @@ sub open_window_with_net_wm_desktop { return $window; } -# We need to kill all windows in between tests since they survive the i3 restart -# and will interfere with the following tests. -sub kill_windows { - sync_with_i3; - cmd '[title="Window.*"] kill'; -} - ############################################################################### my $config = <{floating_nodes}}, 1, 'The window is floating'); ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); -kill_windows; +kill_all_windows; ############################################################################### # _NET_WM_DESKTOP is updated when the window is moved to another workspace @@ -159,7 +152,7 @@ cmd 'move window to workspace 1'; is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); -kill_windows; +kill_all_windows; ############################################################################### # _NET_WM_DESKTOP is updated when the floating window is moved to another @@ -178,7 +171,7 @@ cmd 'move window to workspace 1'; is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); -kill_windows; +kill_all_windows; ############################################################################### # _NET_WM_DESKTOP is removed when the window is withdrawn. @@ -192,7 +185,7 @@ wait_for_unmap($con); is(get_net_wm_desktop($con), undef, '_NET_WM_DESKTOP is removed'); -kill_windows; +kill_all_windows; ############################################################################### # A _NET_WM_DESKTOP client message sent to the root window moves a window @@ -214,7 +207,7 @@ is_num_children('0', 1, 'The window is no longer on workspace 0'); is_num_children('1', 2, 'The window is now on workspace 1'); is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); -kill_windows; +kill_all_windows; ############################################################################### # A _NET_WM_DESKTOP client message sent to the root window can make a window @@ -230,7 +223,7 @@ is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); is(@{get_ws('0')->{floating_nodes}}, 1, 'The window is floating'); ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); -kill_windows; +kill_all_windows; ############################################################################### # _NET_WM_DESKTOP is updated when a new workspace with a lower number is @@ -244,7 +237,7 @@ is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); cmd 'workspace 0'; is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); -kill_windows; +kill_all_windows; ############################################################################### # _NET_WM_DESKTOP is updated when a window is made sticky by command. @@ -258,7 +251,7 @@ is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); cmd 'sticky enable'; is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); -kill_windows; +kill_all_windows; ############################################################################### # _NET_WM_DESKTOP is updated when a window is made sticky by client message. @@ -282,7 +275,7 @@ sync_with_i3; is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); -kill_windows; +kill_all_windows; ############################################################################### # _NET_WM_DESKTOP is updated when a window is moved to the scratchpad. @@ -299,7 +292,7 @@ is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); cmd 'scratchpad show'; is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); -kill_windows; +kill_all_windows; ############################################################################### From 5d9db61dda8d114df4d00a083c8cabd27651a838 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 14 Sep 2017 17:49:02 +0200 Subject: [PATCH 085/123] Reorder tests to not use the same number (#2947) Distinct numbers make re-running individual tests easier by helping with tab-completion. Completeness verified using: % for i in $(seq 0 600) do files=$(ls testcases/t/$(printf "%03d" $i)-*.t 2>&- | wc -l) [ "$files" != "0" ] && [ "$files" != "1" ] && echo "clash: $i" done --- ...or_window_tilingfloating.t => 271-for_window_tilingfloating.t} | 0 .../t/{173-regress-focus-assign.t => 272-regress-focus-assign.t} | 0 .../t/{174-regress-focus-toggle.t => 273-regress-focus-toggle.t} | 0 .../t/{213-move-branch-position.t => 274-move-branch-position.t} | 0 testcases/t/{231-ipc-window-close.t => 275-ipc-window-close.t} | 0 testcases/t/{231-ipc-window-move.t => 276-ipc-window-move.t} | 0 testcases/t/{232-ipc-window-urgent.t => 277-ipc-window-urgent.t} | 0 .../{234-layout-restore-output.t => 278-layout-restore-output.t} | 0 ...lt-floating-border.t => 279-regress-default-floating-border.t} | 0 ...35-wm-class-change-handler.t => 280-wm-class-change-handler.t} | 0 ...{238-regress-reload-bindsym.t => 281-regress-reload-bindsym.t} | 0 ...oating-disable-crash.t => 282-tabbed-floating-disable-crash.t} | 0 .../t/{243-net-wm-state-hidden.t => 283-net-wm-state-hidden.t} | 0 testcases/t/{251-ewmh-visible-name.t => 284-ewmh-visible-name.t} | 0 testcases/t/{251-sticky.t => 285-sticky.t} | 0 ...oot-window-mouse-binding.t => 286-root-window-mouse-binding.t} | 0 testcases/t/{263-edge-borders.t => 287-edge-borders.t} | 0 ...63-i3-floating-window-atom.t => 288-i3-floating-window-atom.t} | 0 .../t/{264-ipc-shutdown-event.t => 289-ipc-shutdown-event.t} | 0 testcases/t/{264-keypress-numlock.t => 290-keypress-numlock.t} | 0 testcases/t/{265-swap.t => 291-swap.t} | 0 .../t/{528-workspace-next-prev.t => 535-workspace-next-prev.t} | 0 testcases/t/{529-net-wm-desktop_mm.t => 536-net-wm-desktop_mm.t} | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename testcases/t/{165-for_window_tilingfloating.t => 271-for_window_tilingfloating.t} (100%) rename testcases/t/{173-regress-focus-assign.t => 272-regress-focus-assign.t} (100%) rename testcases/t/{174-regress-focus-toggle.t => 273-regress-focus-toggle.t} (100%) rename testcases/t/{213-move-branch-position.t => 274-move-branch-position.t} (100%) rename testcases/t/{231-ipc-window-close.t => 275-ipc-window-close.t} (100%) rename testcases/t/{231-ipc-window-move.t => 276-ipc-window-move.t} (100%) rename testcases/t/{232-ipc-window-urgent.t => 277-ipc-window-urgent.t} (100%) rename testcases/t/{234-layout-restore-output.t => 278-layout-restore-output.t} (100%) rename testcases/t/{234-regress-default-floating-border.t => 279-regress-default-floating-border.t} (100%) rename testcases/t/{235-wm-class-change-handler.t => 280-wm-class-change-handler.t} (100%) rename testcases/t/{238-regress-reload-bindsym.t => 281-regress-reload-bindsym.t} (100%) rename testcases/t/{240-tabbed-floating-disable-crash.t => 282-tabbed-floating-disable-crash.t} (100%) rename testcases/t/{243-net-wm-state-hidden.t => 283-net-wm-state-hidden.t} (100%) rename testcases/t/{251-ewmh-visible-name.t => 284-ewmh-visible-name.t} (100%) rename testcases/t/{251-sticky.t => 285-sticky.t} (100%) rename testcases/t/{262-root-window-mouse-binding.t => 286-root-window-mouse-binding.t} (100%) rename testcases/t/{263-edge-borders.t => 287-edge-borders.t} (100%) rename testcases/t/{263-i3-floating-window-atom.t => 288-i3-floating-window-atom.t} (100%) rename testcases/t/{264-ipc-shutdown-event.t => 289-ipc-shutdown-event.t} (100%) rename testcases/t/{264-keypress-numlock.t => 290-keypress-numlock.t} (100%) rename testcases/t/{265-swap.t => 291-swap.t} (100%) rename testcases/t/{528-workspace-next-prev.t => 535-workspace-next-prev.t} (100%) rename testcases/t/{529-net-wm-desktop_mm.t => 536-net-wm-desktop_mm.t} (100%) diff --git a/testcases/t/165-for_window_tilingfloating.t b/testcases/t/271-for_window_tilingfloating.t similarity index 100% rename from testcases/t/165-for_window_tilingfloating.t rename to testcases/t/271-for_window_tilingfloating.t diff --git a/testcases/t/173-regress-focus-assign.t b/testcases/t/272-regress-focus-assign.t similarity index 100% rename from testcases/t/173-regress-focus-assign.t rename to testcases/t/272-regress-focus-assign.t diff --git a/testcases/t/174-regress-focus-toggle.t b/testcases/t/273-regress-focus-toggle.t similarity index 100% rename from testcases/t/174-regress-focus-toggle.t rename to testcases/t/273-regress-focus-toggle.t diff --git a/testcases/t/213-move-branch-position.t b/testcases/t/274-move-branch-position.t similarity index 100% rename from testcases/t/213-move-branch-position.t rename to testcases/t/274-move-branch-position.t diff --git a/testcases/t/231-ipc-window-close.t b/testcases/t/275-ipc-window-close.t similarity index 100% rename from testcases/t/231-ipc-window-close.t rename to testcases/t/275-ipc-window-close.t diff --git a/testcases/t/231-ipc-window-move.t b/testcases/t/276-ipc-window-move.t similarity index 100% rename from testcases/t/231-ipc-window-move.t rename to testcases/t/276-ipc-window-move.t diff --git a/testcases/t/232-ipc-window-urgent.t b/testcases/t/277-ipc-window-urgent.t similarity index 100% rename from testcases/t/232-ipc-window-urgent.t rename to testcases/t/277-ipc-window-urgent.t diff --git a/testcases/t/234-layout-restore-output.t b/testcases/t/278-layout-restore-output.t similarity index 100% rename from testcases/t/234-layout-restore-output.t rename to testcases/t/278-layout-restore-output.t diff --git a/testcases/t/234-regress-default-floating-border.t b/testcases/t/279-regress-default-floating-border.t similarity index 100% rename from testcases/t/234-regress-default-floating-border.t rename to testcases/t/279-regress-default-floating-border.t diff --git a/testcases/t/235-wm-class-change-handler.t b/testcases/t/280-wm-class-change-handler.t similarity index 100% rename from testcases/t/235-wm-class-change-handler.t rename to testcases/t/280-wm-class-change-handler.t diff --git a/testcases/t/238-regress-reload-bindsym.t b/testcases/t/281-regress-reload-bindsym.t similarity index 100% rename from testcases/t/238-regress-reload-bindsym.t rename to testcases/t/281-regress-reload-bindsym.t diff --git a/testcases/t/240-tabbed-floating-disable-crash.t b/testcases/t/282-tabbed-floating-disable-crash.t similarity index 100% rename from testcases/t/240-tabbed-floating-disable-crash.t rename to testcases/t/282-tabbed-floating-disable-crash.t diff --git a/testcases/t/243-net-wm-state-hidden.t b/testcases/t/283-net-wm-state-hidden.t similarity index 100% rename from testcases/t/243-net-wm-state-hidden.t rename to testcases/t/283-net-wm-state-hidden.t diff --git a/testcases/t/251-ewmh-visible-name.t b/testcases/t/284-ewmh-visible-name.t similarity index 100% rename from testcases/t/251-ewmh-visible-name.t rename to testcases/t/284-ewmh-visible-name.t diff --git a/testcases/t/251-sticky.t b/testcases/t/285-sticky.t similarity index 100% rename from testcases/t/251-sticky.t rename to testcases/t/285-sticky.t diff --git a/testcases/t/262-root-window-mouse-binding.t b/testcases/t/286-root-window-mouse-binding.t similarity index 100% rename from testcases/t/262-root-window-mouse-binding.t rename to testcases/t/286-root-window-mouse-binding.t diff --git a/testcases/t/263-edge-borders.t b/testcases/t/287-edge-borders.t similarity index 100% rename from testcases/t/263-edge-borders.t rename to testcases/t/287-edge-borders.t diff --git a/testcases/t/263-i3-floating-window-atom.t b/testcases/t/288-i3-floating-window-atom.t similarity index 100% rename from testcases/t/263-i3-floating-window-atom.t rename to testcases/t/288-i3-floating-window-atom.t diff --git a/testcases/t/264-ipc-shutdown-event.t b/testcases/t/289-ipc-shutdown-event.t similarity index 100% rename from testcases/t/264-ipc-shutdown-event.t rename to testcases/t/289-ipc-shutdown-event.t diff --git a/testcases/t/264-keypress-numlock.t b/testcases/t/290-keypress-numlock.t similarity index 100% rename from testcases/t/264-keypress-numlock.t rename to testcases/t/290-keypress-numlock.t diff --git a/testcases/t/265-swap.t b/testcases/t/291-swap.t similarity index 100% rename from testcases/t/265-swap.t rename to testcases/t/291-swap.t diff --git a/testcases/t/528-workspace-next-prev.t b/testcases/t/535-workspace-next-prev.t similarity index 100% rename from testcases/t/528-workspace-next-prev.t rename to testcases/t/535-workspace-next-prev.t diff --git a/testcases/t/529-net-wm-desktop_mm.t b/testcases/t/536-net-wm-desktop_mm.t similarity index 100% rename from testcases/t/529-net-wm-desktop_mm.t rename to testcases/t/536-net-wm-desktop_mm.t From 28bfeadbb0e3b2f9da282d905cc4272ba749ad10 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 14 Sep 2017 11:44:49 +0200 Subject: [PATCH 086/123] =?UTF-8?q?i3test::XTEST:=20don=E2=80=99t=20?= =?UTF-8?q?=E2=80=9Cuse=20i3test=E2=80=9D=20to=20avoid=20clobbering=20stat?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, the $i3_autostart variable was accidentally overridden. --- testcases/lib/i3test.pm.in | 9 +-------- testcases/lib/i3test/Util.pm | 23 +++++++++++++++++++++++ testcases/lib/i3test/XTEST.pm | 3 ++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 5e3f8b2d..0f28a7a9 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -793,14 +793,7 @@ sub get_socket_path { if ($cache && defined($_cached_socket_path)) { return $_cached_socket_path; } - - my $atom = $x->atom(name => 'I3_SOCKET_PATH'); - my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256); - my $reply = $x->get_property_reply($cookie->{sequence}); - my $socketpath = $reply->{value}; - if ($socketpath eq "/tmp/nested-$ENV{DISPLAY}") { - $socketpath .= '-activation'; - } + my $socketpath = i3test::Util::get_socket_path($x); $_cached_socket_path = $socketpath; return $socketpath; } diff --git a/testcases/lib/i3test/Util.pm b/testcases/lib/i3test/Util.pm index 74913681..725ca4e1 100644 --- a/testcases/lib/i3test/Util.pm +++ b/testcases/lib/i3test/Util.pm @@ -5,9 +5,13 @@ use strict; use warnings; use v5.10; +use X11::XCB qw(GET_PROPERTY_TYPE_ANY); +use X11::XCB::Connection; + use Exporter qw(import); our @EXPORT = qw( slurp + get_socket_path ); =encoding utf-8 @@ -38,6 +42,25 @@ sub slurp { return $content; } +=head2 get_socket_path([X11::XCB::Connection]) + +Gets the socket path from the C atom stored on the X11 root +window. + +=cut +sub get_socket_path { + my ($x) = @_; + $x //= X11::XCB::Connection->new(); + my $atom = $x->atom(name => 'I3_SOCKET_PATH'); + my $cookie = $x->get_property(0, $x->get_root_window(), $atom->id, GET_PROPERTY_TYPE_ANY, 0, 256); + my $reply = $x->get_property_reply($cookie->{sequence}); + my $socketpath = $reply->{value}; + if ($socketpath eq "/tmp/nested-$ENV{DISPLAY}") { + $socketpath .= '-activation'; + } + return $socketpath; +} + =head1 AUTHOR Michael Stapelberg diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 3937b70a..b7a9cdc5 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -5,7 +5,8 @@ use strict; use warnings; use v5.10; -use i3test i3_autostart => 0; +use Test::More; +use i3test::Util qw(get_socket_path); use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib); use AnyEvent::I3; use ExtUtils::PkgConfig; From e5ee11d896007af5c07cfa8060dc514df4414e47 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 14 Sep 2017 12:30:42 +0200 Subject: [PATCH 087/123] tests: use i3_config arg instead of precisely one launch_with_config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way, tests are shorter, and i3test’s invocation of launch_with_config parallelizes work better, using dont_block => 1. --- testcases/lib/SocketActivation.pm | 5 ++ testcases/lib/i3test.pm.in | 5 +- testcases/t/156-fullscreen-focus.t | 18 ++----- testcases/t/186-regress-assign-focus-parent.t | 8 +-- testcases/t/199-ipc-mode-event.t | 8 +-- testcases/t/200-urgency-timer.t | 13 +---- testcases/t/207-shmlog.t | 14 ++---- testcases/t/208-regress-floating-criteria.t | 8 +-- testcases/t/211-regress-urgency-assign.t | 17 +++---- testcases/t/222-regress-dock-resize.t | 11 +---- testcases/t/230-floating-fullscreen-restart.t | 7 +-- .../t/233-regress-manage-focus-unmapped.t | 8 +-- testcases/t/237-regress-assign-focus.t | 8 +-- testcases/t/241-consistent-center.t | 8 +-- ...244-new-workspace-floating-enable-center.t | 8 +-- testcases/t/248-regress-urgency-clear.t | 18 +++---- .../t/254-move-to-output-with-criteria.t | 8 +-- testcases/t/257-keypress-group1-fallback.t | 21 +++----- testcases/t/258-keypress-release.t | 18 +++---- .../t/263-config-reload-reverts-bind-mode.t | 8 +-- testcases/t/264-dock-criteria.t | 7 +-- testcases/t/271-for_window_tilingfloating.t | 17 +++---- testcases/t/272-regress-focus-assign.t | 16 ++---- .../t/279-regress-default-floating-border.t | 8 +-- testcases/t/280-wm-class-change-handler.t | 10 +--- testcases/t/281-regress-reload-bindsym.t | 11 +---- .../t/282-tabbed-floating-disable-crash.t | 8 +-- testcases/t/286-root-window-mouse-binding.t | 9 +--- testcases/t/291-swap.t | 9 +--- testcases/t/500-multi-monitor.t | 6 +-- testcases/t/501-scratchpad.t | 9 +--- testcases/t/502-focus-output.t | 9 +--- testcases/t/503-workspace.t | 7 +-- testcases/t/504-move-workspace-to-output.t | 12 ++--- testcases/t/505-scratchpad-resolution.t | 9 +--- testcases/t/507-workspace-move-crash.t | 11 +---- testcases/t/509-workspace_layout.t | 9 +--- testcases/t/510-focus-across-outputs.t | 11 +---- .../t/511-scratchpad-configure-request.t | 12 +---- testcases/t/512-move-wraps.t | 12 +---- testcases/t/513-move-workspace.t | 14 +----- testcases/t/514-ipc-workspace-multi-monitor.t | 12 +---- testcases/t/515-create-workspace.t | 7 +-- testcases/t/516-move.t | 14 ++---- testcases/t/517-regress-move-direction-ipc.t | 8 +-- testcases/t/518-interpret-workspace-numbers.t | 7 +-- testcases/t/519-mouse-warping.t | 15 ++---- .../t/520-regress-focus-direction-floating.t | 8 +-- testcases/t/521-ewmh-desktop-viewport.t | 8 +-- testcases/t/522-rename-assigned-workspace.t | 7 +-- testcases/t/523-move-position-center.t | 8 +-- testcases/t/524-move.t | 8 +-- testcases/t/525-i3bar-mouse-bindings.t | 49 +++++++++++++------ testcases/t/526-reconfigure-dock.t | 19 +++---- .../t/528-workspace-next-prev-reversed.t | 17 +++---- testcases/t/529-net-wm-desktop.t | 24 +++------ testcases/t/530-bug-2229.t | 8 +-- testcases/t/531-fullscreen-on-given-output.t | 10 +--- testcases/t/534-dont-warp.t | 12 +---- testcases/t/535-workspace-next-prev.t | 17 +++---- testcases/t/536-net-wm-desktop_mm.t | 32 +++++------- 61 files changed, 187 insertions(+), 538 deletions(-) diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 5951fd26..5a5a4484 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -49,6 +49,11 @@ sub activate_i3 { die "could not fork()"; } if ($pid == 0) { + # Start a process group so that in the parent, we can kill the entire + # process group and immediately kill i3bar and any other child + # processes. + setpgrp; + $ENV{LISTEN_PID} = $$; $ENV{LISTEN_FDS} = 1; delete $ENV{DESKTOP_STARTUP_ID}; diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 0f28a7a9..a9cfba37 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -126,7 +126,7 @@ END { exit_gracefully($i3_pid, "/tmp/nested-$ENV{DISPLAY}"); } else { - kill(9, $i3_pid) + kill(-9, $i3_pid) or $tester->BAIL_OUT("could not kill i3"); waitpid $i3_pid, 0; @@ -138,8 +138,9 @@ sub import { my $pkg = caller; $i3_autostart = delete($args{i3_autostart}) // 1; + my $i3_config = delete($args{i3_config}) // '-default'; - my $cv = launch_with_config('-default', dont_block => 1) + my $cv = launch_with_config($i3_config, dont_block => 1) if $i3_autostart; my $test_more_args = ''; diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t index b337de9a..eefdac9f 100644 --- a/testcases/t/156-fullscreen-focus.t +++ b/testcases/t/156-fullscreen-focus.t @@ -18,22 +18,16 @@ # the time of launching the new one. Also make sure that focusing containers # in other workspaces work even when there is a fullscreen container. # -use i3test i3_autostart => 0; - -# Screen setup looks like this: -# +----+----+ -# | S1 | S2 | -# +----+----+ -my $config = < < 0; - -my $config = < < "testcase"); is_num_children('targetws', 3, 'new window opened next to last one'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/199-ipc-mode-event.t b/testcases/t/199-ipc-mode-event.t index 43f7b178..62675b44 100644 --- a/testcases/t/199-ipc-mode-event.t +++ b/testcases/t/199-ipc-mode-event.t @@ -15,9 +15,7 @@ # (unless you are already familiar with Perl) # # Verifies that the IPC 'mode' event is sent when modes are changed. -use i3test i3_autostart => 0; - -my $config = < <connect->recv; @@ -52,6 +48,4 @@ $t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); }); ok($cv->recv, 'Mode event received'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/200-urgency-timer.t b/testcases/t/200-urgency-timer.t index b6ebc876..bd0b4078 100644 --- a/testcases/t/200-urgency-timer.t +++ b/testcases/t/200-urgency-timer.t @@ -20,20 +20,13 @@ # use List::Util qw(first); -use i3test i3_autostart => 0; -use Time::HiRes qw(sleep); - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < < 0; +use i3test; use IPC::Run qw(run); use File::Temp; @@ -22,14 +22,8 @@ use File::Temp; # 1: test that shared memory logging does not work yet ################################################################################ -my $config = < 0; - -my $config = <<'EOT'; +use i3test i3_config => < 'Borderless window', wm_class => 'special', @@ -43,6 +39,4 @@ cmd '[class="^special$"] focus'; does_i3_live; -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/211-regress-urgency-assign.t b/testcases/t/211-regress-urgency-assign.t index 57d8b434..1e1df7be 100644 --- a/testcases/t/211-regress-urgency-assign.t +++ b/testcases/t/211-regress-urgency-assign.t @@ -20,7 +20,12 @@ # # Ticket: #1086 # Bug still in: 4.6-62-g7098ef6 -use i3test i3_autostart => 0; +use i3test i3_config => < 0, my @urgent = grep { $_->{urgent} } @{get_ws_content('nonvisible')}; isnt(@urgent, 0, 'urgent window(s) found on destination workspace'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/222-regress-dock-resize.t b/testcases/t/222-regress-dock-resize.t index b9f9a797..bfd7aa4b 100644 --- a/testcases/t/222-regress-dock-resize.t +++ b/testcases/t/222-regress-dock-resize.t @@ -18,18 +18,11 @@ # client. # Ticket: #1201 # Bug still in: 4.7.2-107-g9b03be6 -use i3test i3_autostart => 0; - -my $config = <<'EOT'; +use i3test i3_config => <connect()->recv; - my $window = open_window( wm_class => 'special', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), @@ -39,6 +32,4 @@ cmd('[class="special"] resize grow height 160 px or 16 ppt'); does_i3_live; -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/230-floating-fullscreen-restart.t b/testcases/t/230-floating-fullscreen-restart.t index e4d51e2e..9d1ac70c 100644 --- a/testcases/t/230-floating-fullscreen-restart.t +++ b/testcases/t/230-floating-fullscreen-restart.t @@ -18,16 +18,13 @@ # and that they keep their geometry. # Ticket: #1263 # Bug still in: 4.7.2-200-g570b572 -use i3test i3_autostart => 0; - -my $config = < < '__i3-test-window'); @@ -46,6 +43,4 @@ $floating_win = $nodes->{floating_nodes}->[0]->{nodes}->[0]; is($floating_win->{fullscreen_mode}, 1, 'floating window still in fullscreen mode'); is_deeply($floating_win->{geometry}, $old_geometry, 'floating window geometry still the same'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/233-regress-manage-focus-unmapped.t b/testcases/t/233-regress-manage-focus-unmapped.t index 1b78f253..3f47970c 100644 --- a/testcases/t/233-regress-manage-focus-unmapped.t +++ b/testcases/t/233-regress-manage-focus-unmapped.t @@ -19,9 +19,7 @@ # which can lead to complications # Ticket: #1283 # Bug still in: 4.8-24-g60070de -use i3test i3_autostart => 0; - -my $config = <<'EOT'; +use i3test i3_config => <<'EOT'; # i3 config file (v4) font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 @@ -29,8 +27,6 @@ for_window [class="^special_kill$"] kill for_window [class="^special_scratchpad$"] move scratchpad EOT -my $pid = launch_with_config($config); - my $win = open_window; my $scratch_window = open_window( @@ -53,6 +49,4 @@ sync_with_i3; is($x->input_focus, $win->{id}, 'an assignment that kills a window should not disturb focus'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/237-regress-assign-focus.t b/testcases/t/237-regress-assign-focus.t index e9cb537e..e193c021 100644 --- a/testcases/t/237-regress-assign-focus.t +++ b/testcases/t/237-regress-assign-focus.t @@ -18,9 +18,7 @@ # assigned to an invisible workspace) will not crash i3. # Ticket: #1338 # Bug still in: 4.8-91-g294d52e -use i3test i3_autostart => 0; - -my $config = < < 0; - -my $config = < <rect; is($new->{x}, $child->{x}, 'x coordinates match'); is($new->{y}, $child->{y}, 'y coordinates match'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/244-new-workspace-floating-enable-center.t b/testcases/t/244-new-workspace-floating-enable-center.t index dbc9a80a..b1c7fca0 100644 --- a/testcases/t/244-new-workspace-floating-enable-center.t +++ b/testcases/t/244-new-workspace-floating-enable-center.t @@ -17,9 +17,7 @@ # Ensures that 'move workspace $new, floating enable' on a marked window # leaves the window centered on the new workspace. # Bug still in: 4.10.2-137-ga4f0ed6 -use i3test i3_autostart => 0; - -my $config = < <{x} + $pos->{width} / 2), int($x->root->rect->width / 2), is(int($pos->{y} + $pos->{height} / 2), int($x->root->rect->height / 2), 'y coordinates match'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/248-regress-urgency-clear.t b/testcases/t/248-regress-urgency-clear.t index 10ef3774..314a8511 100644 --- a/testcases/t/248-regress-urgency-clear.t +++ b/testcases/t/248-regress-urgency-clear.t @@ -18,7 +18,12 @@ # to focus_on_window_activation=urgent), hence the application not clearing it. # Ticket: #1825 # Bug still in: 4.10.3-253-g03799dd -use i3test i3_autostart => 0; +use i3test i3_config => <send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); } -my $config = <<'EOT'; -# i3 config file (v4) -font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 - -focus_on_window_activation urgent -EOT - -my $pid = launch_with_config($config); -my $i3 = i3(get_socket_path(0)); my $ws = fresh_workspace; my $first = open_window; my $second = open_window; @@ -64,6 +60,4 @@ cmd '[urgent=latest] focus'; sync_with_i3; is($x->input_focus, $second->id, 'second window still focused'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/254-move-to-output-with-criteria.t b/testcases/t/254-move-to-output-with-criteria.t index b27ded3e..7d8229dd 100644 --- a/testcases/t/254-move-to-output-with-criteria.t +++ b/testcases/t/254-move-to-output-with-criteria.t @@ -17,17 +17,13 @@ # Verifies that "move container to output" works correctly when # used with command criteria. # Bug still in: 4.10.4-349-gee5db87 -use i3test i3_autostart => 0; - -my $config = < < 0); my $ws_top_right = fresh_workspace(output => 1); my $ws_bottom_left = fresh_workspace(output => 2); @@ -45,6 +41,4 @@ is_num_children($ws_top_right, 1, 'one container on the upper right workspace'); is_num_children($ws_bottom_left, 0, 'no containers on the lower left workspace'); is_num_children($ws_bottom_right, 1, 'one container on the lower right workspace'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t index 843b8fe6..1c19cb78 100644 --- a/testcases/t/257-keypress-group1-fallback.t +++ b/testcases/t/257-keypress-group1-fallback.t @@ -19,7 +19,14 @@ # Ticket: #2062 # Bug still in: 4.11-103-gc8d51b4 # Bug introduced with commit 0e5180cae9e9295678e3f053042b559e82cb8c98 -use i3test i3_autostart => 0; +use i3test + i3_config => </dev/null|) != 0; -my $config = < 0; -use i3test::XTEST; -use ExtUtils::PkgConfig; - -SKIP: { - skip "libxcb-xkb too old (need >= 1.11)", 1 unless - ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); - -my $config = < <= 1.11)", 1 unless + ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); start_binding_capture; @@ -90,8 +86,6 @@ is(listen_for_binding( sync_with_i3; is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events'); -exit_gracefully($pid); - } done_testing; diff --git a/testcases/t/263-config-reload-reverts-bind-mode.t b/testcases/t/263-config-reload-reverts-bind-mode.t index d72d3d94..549a862b 100644 --- a/testcases/t/263-config-reload-reverts-bind-mode.t +++ b/testcases/t/263-config-reload-reverts-bind-mode.t @@ -18,9 +18,7 @@ # binding mode. # Ticket: #2228 # Bug still in: 4.11-262-geb631ce -use i3test i3_autostart => 0; - -my $config = < <timer(after => 0.5, cb => sub { $cv->send(0); }); ok($cv->recv, 'Mode event received'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/264-dock-criteria.t b/testcases/t/264-dock-criteria.t index cbbdeb0f..d4b3b689 100644 --- a/testcases/t/264-dock-criteria.t +++ b/testcases/t/264-dock-criteria.t @@ -16,15 +16,11 @@ # # Verifies that command or config criteria does not match dock clients # Bug still in: 4.12-38-ge690e3d -use i3test i3_autostart => 0; - -my $config = < < 0; +use i3test i3_config => <{marks}, [ 'tiled' ], "mark set for 'tiling' criterion"); cmp_ok(@nodes, '==', 1, 'one floating container on this workspace'); is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floated' ], "mark set for 'floating' criterion"); -exit_gracefully($pid); - ############################################################## done_testing; diff --git a/testcases/t/272-regress-focus-assign.t b/testcases/t/272-regress-focus-assign.t index b010963b..6b0aad0c 100644 --- a/testcases/t/272-regress-focus-assign.t +++ b/testcases/t/272-regress-focus-assign.t @@ -17,7 +17,11 @@ # Regression: Checks if focus is stolen when a window is managed which is # assigned to an invisible workspace # -use i3test i3_autostart => 0; +use i3test i3_config => <{focused}, 'current workspace still focused'); -exit_gracefully($pid); - $window->destroy; done_testing; diff --git a/testcases/t/279-regress-default-floating-border.t b/testcases/t/279-regress-default-floating-border.t index d5994f58..5563ec32 100644 --- a/testcases/t/279-regress-default-floating-border.t +++ b/testcases/t/279-regress-default-floating-border.t @@ -18,9 +18,7 @@ # not applied when the default tiling border is set to a pixel value. # Ticket: #1305 # Bug still in: 4.8-62-g7381b50 -use i3test i3_autostart => 0; - -my $config = < <{floating_nodes}}; is($floating[0]->{nodes}[0]->{border}, 'normal', 'default floating border is `normal`'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/280-wm-class-change-handler.t b/testcases/t/280-wm-class-change-handler.t index ce237b57..38b8351d 100644 --- a/testcases/t/280-wm-class-change-handler.t +++ b/testcases/t/280-wm-class-change-handler.t @@ -19,16 +19,12 @@ # in criteria selection # Ticket: #1052 # Bug still in: 4.8-73-g6bf7f8e -use i3test i3_autostart => 0; -use X11::XCB qw(PROP_MODE_REPLACE); - -my $config = < <{window_properties}->{class}, 'a', 'Non-null-terminated strings should be handled correctly'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/281-regress-reload-bindsym.t b/testcases/t/281-regress-reload-bindsym.t index 6d5d12c3..ad81244e 100644 --- a/testcases/t/281-regress-reload-bindsym.t +++ b/testcases/t/281-regress-reload-bindsym.t @@ -16,9 +16,7 @@ # # Test that the binding event works properly # Ticket: #1210 -use i3test i3_autostart => 0; - -my $config = < <connect->recv; - qx(xdotool key r); does_i3_live; - exit_gracefully($pid); - } done_testing; diff --git a/testcases/t/282-tabbed-floating-disable-crash.t b/testcases/t/282-tabbed-floating-disable-crash.t index 7947158c..b4f1a4c2 100644 --- a/testcases/t/282-tabbed-floating-disable-crash.t +++ b/testcases/t/282-tabbed-floating-disable-crash.t @@ -18,16 +18,12 @@ # unfocused window within a tabbed container. # Ticket: #1484 # Bug still in: 4.9.1-124-g856e1f9 -use i3test i3_autostart => 0; - -my $config = < < 0; -use i3test::XTEST; - -my $config = < < 0; - -my $config = < < 0; - -my $config = < <{name} } @{$tree->{nodes}}; is_deeply(\@outputs, [ '__i3', 'fake-0', 'fake-1' ], 'multi-monitor outputs ok'); -exit_gracefully($pid); done_testing; diff --git a/testcases/t/501-scratchpad.t b/testcases/t/501-scratchpad.t index 0f9b0df0..5aad2504 100644 --- a/testcases/t/501-scratchpad.t +++ b/testcases/t/501-scratchpad.t @@ -18,17 +18,12 @@ # ticket #596, bug present until up to commit # 89dded044b4fffe78f9d70778748fabb7ac533e9. # -use i3test i3_autostart => 0; - -my $config = < < 0); verify_scratchpad_switch($first, $second); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/502-focus-output.t b/testcases/t/502-focus-output.t index cf297f0e..4b6fac40 100644 --- a/testcases/t/502-focus-output.t +++ b/testcases/t/502-focus-output.t @@ -16,16 +16,13 @@ # # Verifies the 'focus output' command works properly. -use i3test i3_autostart => 0; -use List::Util qw(first); - -my $config = < < 0; - -my $config = < < to [output] ' command works # use List::Util qw(first); -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < < 0; - -my $config = < <root->warp_pointer(0, 0); @@ -84,6 +79,4 @@ sync_with_i3; my $third = fresh_workspace(output => 1); verify_scratchpad_doesnt_move($third); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/507-workspace-move-crash.t b/testcases/t/507-workspace-move-crash.t index 9e80553b..ed5a9005 100644 --- a/testcases/t/507-workspace-move-crash.t +++ b/testcases/t/507-workspace-move-crash.t @@ -19,19 +19,12 @@ # Bug still in: 4.3-78-g66b389c # use List::Util qw(first); -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < < 0; - -my $config = < <{workspace_layout}, 'tabbed', 'workspace layout is "tabbed"'); - -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/510-focus-across-outputs.t b/testcases/t/510-focus-across-outputs.t index afa0ddef..8097f73f 100644 --- a/testcases/t/510-focus-across-outputs.t +++ b/testcases/t/510-focus-across-outputs.t @@ -17,19 +17,12 @@ # Tests that switching workspaces via 'focus $dir' never leaves a floating # window focused. # -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < <input_focus, $second->id, 'second window focused'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/511-scratchpad-configure-request.t b/testcases/t/511-scratchpad-configure-request.t index 561d2435..851afb4a 100644 --- a/testcases/t/511-scratchpad-configure-request.t +++ b/testcases/t/511-scratchpad-configure-request.t @@ -17,21 +17,13 @@ # Tests that ConfigureRequests don’t make windows fall out of the scratchpad. # Ticket: #898 # Bug still in: 4.4-15-g770ead6 -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < < 0); my $right_ws = fresh_workspace(output => 1); @@ -49,6 +41,4 @@ is(scalar @{$ws->{floating_nodes}}, 0, 'scratchpad window still in scratchpad af $ws = get_ws($right_ws); is(scalar @{$ws->{floating_nodes}}, 0, 'scratchpad window still in scratchpad after ConfigureRequest'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/512-move-wraps.t b/testcases/t/512-move-wraps.t index 82d5a7cc..2c5a27af 100644 --- a/testcases/t/512-move-wraps.t +++ b/testcases/t/512-move-wraps.t @@ -18,21 +18,13 @@ # E.g. when you have a container on the right output and you move it to the # right, it should appear on the left output. # Bug still in: 4.4-106-g3cd4b8c -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < < 1); my $left = fresh_workspace(output => 0); @@ -51,6 +43,4 @@ cmd 'move container to output right'; is_num_children($left, 1, 'one container on left workspace'); is_num_children($right, 0, 'no containers on right workspace'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/513-move-workspace.t b/testcases/t/513-move-workspace.t index 3e27a6c0..fb9f0e25 100644 --- a/testcases/t/513-move-workspace.t +++ b/testcases/t/513-move-workspace.t @@ -15,21 +15,13 @@ # (unless you are already familiar with Perl) # # Tests whether moving workspaces between outputs works correctly. -use i3test i3_autostart => 0; -use List::Util qw(first); - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < <[1]->{window}, $win1->id, 'window 1 on workspace 5 after moving'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/514-ipc-workspace-multi-monitor.t b/testcases/t/514-ipc-workspace-multi-monitor.t index 61622ab0..f34ee233 100644 --- a/testcases/t/514-ipc-workspace-multi-monitor.t +++ b/testcases/t/514-ipc-workspace-multi-monitor.t @@ -17,20 +17,12 @@ # Ticket: #990 # Bug still in: 4.5.1-23-g82b5978 -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < <connect()->recv; @@ -73,6 +65,4 @@ ok($event, 'Workspace "focus" event received'); is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace'); is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/515-create-workspace.t b/testcases/t/515-create-workspace.t index be790bf0..aad0693f 100644 --- a/testcases/t/515-create-workspace.t +++ b/testcases/t/515-create-workspace.t @@ -17,9 +17,7 @@ # Tests that new workspace names are taken from the config, # then from the first free number starting with 1. # -use i3test i3_autostart => 0; - -my $config = < <get_workspaces->recv; @@ -35,6 +32,4 @@ my $ws = $i3->get_workspaces->recv; is($ws->[0]->{name}, '1: eggs', 'new workspace uses config name'); is($ws->[1]->{name}, '2', 'naming continues with next free number'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/516-move.t b/testcases/t/516-move.t index 0d21ca31..3a4fa2ea 100644 --- a/testcases/t/516-move.t +++ b/testcases/t/516-move.t @@ -16,13 +16,7 @@ # # Tests if a simple 'move ' command will move containers across outputs. # -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < <root->warp_pointer(0, 0); ##################################################################### # Try to move a single window across outputs in each direction @@ -101,6 +97,4 @@ is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom wo $compare_window = shift @{get_ws_content('left-top')}; is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/517-regress-move-direction-ipc.t b/testcases/t/517-regress-move-direction-ipc.t index 383b2779..1a325ae7 100644 --- a/testcases/t/517-regress-move-direction-ipc.t +++ b/testcases/t/517-regress-move-direction-ipc.t @@ -18,9 +18,7 @@ # ipc event required for i3bar to be properly updated and redrawn. # # Bug still in: 4.6-195-g34232b8 -use i3test i3_autostart => 0; - -my $config = < <connect()->recv; @@ -78,6 +74,4 @@ ok($event, 'moving from workspace with one window triggered focus ipc event'); is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace'); is(@{$event->{current}->{nodes}}, 2, 'focus event gave the right number of windows on the workspace'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/518-interpret-workspace-numbers.t b/testcases/t/518-interpret-workspace-numbers.t index 577881f0..aa2e2a27 100644 --- a/testcases/t/518-interpret-workspace-numbers.t +++ b/testcases/t/518-interpret-workspace-numbers.t @@ -18,9 +18,7 @@ # assign any workspace of that number to the specified output. # Ticket: #1238 # Bug still in: 4.7.2-147-g3760a48 -use i3test i3_autostart => 0; - -my $config = < <connect->recv; @@ -72,6 +69,4 @@ is(get_output_for_workspace('1:override'), 'fake-0', 'Assignment rules should not be affected by the order assignments are declared') or diag 'Since workspace "1:override" is assigned by name to fake-0, it should open on fake-0'; -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/519-mouse-warping.t b/testcases/t/519-mouse-warping.t index 674f4cdb..5b7e2c17 100644 --- a/testcases/t/519-mouse-warping.t +++ b/testcases/t/519-mouse-warping.t @@ -11,21 +11,16 @@ # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf # (unless you are already familiar with Perl) -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < <root->warp_pointer(0, 0); ###################################################### # Open one workspace with one window on both outputs # @@ -47,6 +42,4 @@ $x->root->warp_pointer(0, 0); # Ensure focus is still on workspace 2 is(focused_ws, '2', 'warped mouse cursor to (0, 0), focus still in workspace 2'); -# Exit gracefully -exit_gracefully($pid); done_testing; diff --git a/testcases/t/520-regress-focus-direction-floating.t b/testcases/t/520-regress-focus-direction-floating.t index ccef49e7..871f8045 100644 --- a/testcases/t/520-regress-focus-direction-floating.t +++ b/testcases/t/520-regress-focus-direction-floating.t @@ -17,9 +17,7 @@ # Ensure that `focus [direction]` will focus an existing floating con when no # tiling con exists on the output in [direction] when focusing across outputs # Bug still in: 4.7.2-204-g893dbae -use i3test i3_autostart => 0; - -my $config = < <input_focus, $win->id, 'Focusing across outputs with `focus [direction]` should focus an existing floating con when no tiling con exists on the output in [direction].'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/521-ewmh-desktop-viewport.t b/testcases/t/521-ewmh-desktop-viewport.t index 9e36090c..27354769 100644 --- a/testcases/t/521-ewmh-desktop-viewport.t +++ b/testcases/t/521-ewmh-desktop-viewport.t @@ -18,9 +18,7 @@ # properly on the root window. We interpret this as a list of x/y coordinate # pairs for the upper left corner of the respective outputs of the workspaces # Ticket: #1241 -use i3test i3_autostart => 0; - -my $config = < < 0; - -my $config = < <connect->recv; @@ -85,6 +82,4 @@ cmd 'rename workspace to baz'; is(get_output_for_workspace('baz'), 'fake-1', 'Renaming the workspace to a number and name should move it to the assigned output'); - -exit_gracefully($pid); done_testing; diff --git a/testcases/t/523-move-position-center.t b/testcases/t/523-move-position-center.t index 6b584245..f0a8a816 100644 --- a/testcases/t/523-move-position-center.t +++ b/testcases/t/523-move-position-center.t @@ -18,9 +18,7 @@ # the appropriate output. # Ticket: #1211 # Bug still in: 4.9.1-108-g037cb31 -use i3test i3_autostart => 0; - -my $config = < <{floating_nodes}}, 0, 'no floating nodes on left ws'); is(scalar @{get_ws('right')->{floating_nodes}}, 1, 'one floating node on right ws'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/524-move.t b/testcases/t/524-move.t index 473bf235..8cc20bc0 100644 --- a/testcases/t/524-move.t +++ b/testcases/t/524-move.t @@ -19,9 +19,7 @@ # Ticket: #1603 # Bug still in: 4.10.1-40-g0ad097e use List::Util qw(first); -use i3test i3_autostart => 0; - -my $config = < < 0; -use i3test::XTEST; - -my ($cv, $timer); -sub reset_test { - $cv = AE::cv; - $timer = AE::timer(1, 0, sub { $cv->send(0); }); -} - -my $config = < <send(0); }); +} -my $pid = launch_with_config($config); my $i3 = i3(get_socket_path()); $i3->connect()->recv; my $ws = fresh_workspace; @@ -63,8 +60,32 @@ $i3->subscribe({ }, })->recv; -my $con = $cv->recv; -ok($con, 'i3bar appeared'); +my $con; + +sub i3bar_present { + my ($nodes) = @_; + + for my $node (@{$nodes}) { + my $props = $node->{window_properties}; + if (defined($props) && $props->{class} eq 'i3bar') { + return 1; + } + } + + return 0 if !@{$nodes}; + + my @children = (map { @{$_->{nodes}} } @{$nodes}, + map { @{$_->{'floating_nodes'}} } @{$nodes}); + + return i3bar_present(\@children); +} + +if (i3bar_present($i3->get_tree->recv->{nodes})) { + ok(1, 'i3bar present'); +} else { + $con = $cv->recv; + ok($con, 'i3bar appeared'); +} my $left = open_window; my $right = open_window; @@ -108,6 +129,4 @@ $con = $cv->recv; is($con->{window}, $left->{id}, 'button 5 moves focus left'); reset_test; -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/526-reconfigure-dock.t b/testcases/t/526-reconfigure-dock.t index 9e3ed3f2..19887ac1 100644 --- a/testcases/t/526-reconfigure-dock.t +++ b/testcases/t/526-reconfigure-dock.t @@ -16,9 +16,14 @@ # # Test reconfiguration of dock clients. # Ticket: #1883 -use i3test i3_autostart => 0; +use i3test i3_config => < $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK') }); @@ -50,8 +47,6 @@ is(@docks, 1, 'there is still exactly one dock'); is($docks[0]->{rect}->{x}, 1024, 'dock client has moved to the other screen'); -exit_gracefully($pid); - ############################################################################### done_testing; diff --git a/testcases/t/528-workspace-next-prev-reversed.t b/testcases/t/528-workspace-next-prev-reversed.t index 00a9bbe4..b10addec 100644 --- a/testcases/t/528-workspace-next-prev-reversed.t +++ b/testcases/t/528-workspace-next-prev-reversed.t @@ -17,7 +17,12 @@ # Tests whether 'workspace next' works correctly. # use List::Util qw(first); -use i3test i3_autostart => 0; +use i3test i3_config => <root->warp_pointer(0, 0); sync_with_i3; @@ -124,6 +121,4 @@ assert_prev('2'); assert_prev('1'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t index 5f858051..63c0b6a6 100644 --- a/testcases/t/529-net-wm-desktop.t +++ b/testcases/t/529-net-wm-desktop.t @@ -16,7 +16,14 @@ # # Tests for _NET_WM_DESKTOP. # Ticket: #2153 -use i3test i3_autostart => 0; +use i3test i3_config => < 0; - -my $config = < <get_workspaces->recv; my @ws_names = map { $_->{name} } @$get_ws; +# TODO get rid of smartmatch ok(!('3' ~~ @ws_names), 'workspace 3 has been closed'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/531-fullscreen-on-given-output.t b/testcases/t/531-fullscreen-on-given-output.t index fd328653..b2040554 100644 --- a/testcases/t/531-fullscreen-on-given-output.t +++ b/testcases/t/531-fullscreen-on-given-output.t @@ -16,17 +16,13 @@ # # Tests that fullscreen windows appear on the output indicated by # their geometry -use i3test i3_autostart => 0; -use List::Util qw(first); - -my $config = < <{nodes}, $win_on_second_output->{id}); is($node1->{fullscreen_mode}, 1, "first window is fullscreen"); is($node2->{fullscreen_mode}, 1, "second window is fullscreen"); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/534-dont-warp.t b/testcases/t/534-dont-warp.t index 8f84f9ae..997340fa 100644 --- a/testcases/t/534-dont-warp.t +++ b/testcases/t/534-dont-warp.t @@ -18,13 +18,7 @@ # over an unfocused workspace. # Ticket: #2681 # Bug still in: 4.13-210-g80c23afa -use i3test i3_autostart => 0; - -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - -my $config = < <query_pointer_reply($cookie->{sequence}); cmp_ok($reply->{root_x}, '<', 1024, 'pointer still on fake-0'); cmp_ok($reply->{root_y}, '<', 768, 'pointer still on fake-0'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/535-workspace-next-prev.t b/testcases/t/535-workspace-next-prev.t index 1a83de23..ac80eab7 100644 --- a/testcases/t/535-workspace-next-prev.t +++ b/testcases/t/535-workspace-next-prev.t @@ -17,7 +17,12 @@ # Tests whether 'workspace next' works correctly. # use List::Util qw(first); -use i3test i3_autostart => 0; +use i3test i3_config => <root->warp_pointer(0, 0); sync_with_i3; @@ -124,6 +121,4 @@ assert_prev('2'); assert_prev('1'); -exit_gracefully($pid); - done_testing; diff --git a/testcases/t/536-net-wm-desktop_mm.t b/testcases/t/536-net-wm-desktop_mm.t index 77238946..6346ebb7 100644 --- a/testcases/t/536-net-wm-desktop_mm.t +++ b/testcases/t/536-net-wm-desktop_mm.t @@ -16,7 +16,19 @@ # # Tests for _NET_WM_DESKTOP. # Ticket: #2153 -use i3test i3_autostart => 0; +use i3test i3_config => <{value}); } -my $config = < Date: Thu, 14 Sep 2017 11:30:58 +0200 Subject: [PATCH 088/123] tests: remove the (broken) exit_gracefully check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I previously tried to fix the check, but could only come up with a fix which required removing our module pre-loading, which makes the tests considerably more expensive. Instead, let’s just remove the check. --- testcases/lib/TestWorker.pm | 9 +++------ testcases/lib/i3test.pm.in | 10 ++-------- testcases/t/000-load-deps.t | 1 - 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/testcases/lib/TestWorker.pm b/testcases/lib/TestWorker.pm index c56767c4..9716f7f1 100644 --- a/testcases/lib/TestWorker.pm +++ b/testcases/lib/TestWorker.pm @@ -47,12 +47,9 @@ sub worker { $worker->{ipc} = $ipc_child; + # Preload the i3test module: reduces user CPU from 25s to 18s require i3test; - # TODO: recycle $x - # unfortunately this fails currently with: - # Could not get reply for: xcb_intern_atom_reply at X11/XCB/Atom.pm line 22. - # $i3test::x = bless $x, 'i3test::X11'; worker_wait($worker, $outdir); exit 23; @@ -86,11 +83,11 @@ sub worker_wait { exit unless $file; - die "tried to launch nonexistend testfile $file: $!\n" + die "tried to launch nonexistent testfile $file: $!\n" unless -e $file; # start a new and self contained process: - # whatever happens in the testfile should *NOT* effect us. + # whatever happens in the testfile should *NOT* affect us. my $pid = fork // die "could not fork: $!"; if ($pid == 0) { diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index a9cfba37..222babcf 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -100,14 +100,8 @@ my $i3_pid; my $i3_autostart; END { - - # testcases which start i3 manually should always call exit_gracefully - # on their own. Let’s see, whether they really did. - if (! $i3_autostart) { - return unless $i3_pid; - - $tester->ok(undef, 'testcase called exit_gracefully()'); - } + # Skip the remaining cleanup for testcases which set i3_autostart => 0: + return if !defined($i3_pid) && !$i3_autostart; # don't trigger SIGCHLD handler local $SIG{CHLD}; diff --git a/testcases/t/000-load-deps.t b/testcases/t/000-load-deps.t index e0408338..65795305 100644 --- a/testcases/t/000-load-deps.t +++ b/testcases/t/000-load-deps.t @@ -11,7 +11,6 @@ BEGIN { IPC::Run ExtUtils::PkgConfig Inline - Test::More ); for my $dep (@deps) { use_ok($dep) or BAIL_OUT(qq|The Perl module "$dep" could not be loaded. Please see http://build.i3wm.org/docs/testsuite.html#_installing_the_dependencies|); From da72a5be91ba9a64b368541a9af9f07d5de38df5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 14 Sep 2017 13:35:26 +0200 Subject: [PATCH 089/123] tests: unflake tests by not starting i3bar --- testcases/t/264-dock-criteria.t | 5 +++++ testcases/t/504-move-workspace-to-output.t | 5 +++++ testcases/t/526-reconfigure-dock.t | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/testcases/t/264-dock-criteria.t b/testcases/t/264-dock-criteria.t index d4b3b689..1eddba71 100644 --- a/testcases/t/264-dock-criteria.t +++ b/testcases/t/264-dock-criteria.t @@ -19,6 +19,11 @@ use i3test i3_config => < < < Date: Tue, 19 Sep 2017 14:33:51 +0000 Subject: [PATCH 090/123] fake_outputs: Use %n format specifier instead of sprintf fake_outputs_init used a sprintf invocation with a throw-away buffer to estimate how many characters the sscanf invocation consumed. This was unnecessary, and also potentially incorrect, as differences between the read and formatted strings (such as leading zeros) could lead to fake_outputs_init to lose its track. Instead, use the %n format specifier which allows saving the number of characters consumed by sscanf so far. %n is part of C99. --- src/fake_outputs.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/fake_outputs.c b/src/fake_outputs.c index 6639b361..447409d2 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -33,10 +33,10 @@ static Output *get_screen_at(unsigned int x, unsigned int y) { * */ void fake_outputs_init(const char *output_spec) { - char useless_buffer[1024]; const char *walk = output_spec; unsigned int x, y, width, height; - while (sscanf(walk, "%ux%u+%u+%u", &width, &height, &x, &y) == 4) { + int chars_consumed; + while (sscanf(walk, "%ux%u+%u+%u%n", &width, &height, &x, &y, &chars_consumed) == 4) { DLOG("Parsed output as width = %u, height = %u at (%u, %u)\n", width, height, x, y); Output *new_output = get_screen_at(x, y); @@ -68,8 +68,7 @@ void fake_outputs_init(const char *output_spec) { num_screens++; } - /* Figure out how long the input was to skip it */ - walk += sprintf(useless_buffer, "%ux%u+%u+%u", width, height, x, y) + 1; + walk += chars_consumed + 1; } if (num_screens == 0) { From c932c88ea920917ebe6fd63f42953cfedd1265c9 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 19 Sep 2017 14:37:35 +0000 Subject: [PATCH 091/123] fake_outputs: Don't read past the end of string fake_outputs_init would unconditionally increase the string read pointer variable (walk) by one character more than the number of characters that have been read, to skip past the character delimiting records (a comma). However, when the input string was not terminated by a comma, it would cause the function to read past the null terminator instead. Avoid this by explicitly checking for the expected delimiter. --- src/fake_outputs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fake_outputs.c b/src/fake_outputs.c index 447409d2..64c3e20c 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -68,7 +68,9 @@ void fake_outputs_init(const char *output_spec) { num_screens++; } - walk += chars_consumed + 1; + walk += chars_consumed; + if (*walk == ',') + walk++; } if (num_screens == 0) { From e4bfb4dae33242550b961a25d58451bd6257bfa4 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 19 Sep 2017 14:42:13 +0000 Subject: [PATCH 092/123] fake_outputs: Allow designating a fake output as primary Allow appending 'P' to the fake output specification to set the created output's "primary" flag, to allow writing test cases that depend on the presence of a primary output. --- src/fake_outputs.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/fake_outputs.c b/src/fake_outputs.c index 64c3e20c..39cbd7bb 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -28,8 +28,9 @@ static Output *get_screen_at(unsigned int x, unsigned int y) { /* * Creates outputs according to the given specification. * The specification must be in the format wxh+x+y, for example 1024x768+0+0, + * optionally followed by 'P' to indicate a primary output, * with multiple outputs separated by commas: - * 1900x1200+0+0,1280x1024+1900+0 + * 1900x1200+0+0P,1280x1024+1900+0 * */ void fake_outputs_init(const char *output_spec) { @@ -37,8 +38,17 @@ void fake_outputs_init(const char *output_spec) { unsigned int x, y, width, height; int chars_consumed; while (sscanf(walk, "%ux%u+%u+%u%n", &width, &height, &x, &y, &chars_consumed) == 4) { - DLOG("Parsed output as width = %u, height = %u at (%u, %u)\n", - width, height, x, y); + walk += chars_consumed; + bool primary = false; + if (*walk == 'P') { + primary = true; + walk++; + } + if (*walk == ',') + walk++; /* Skip delimiter */ + DLOG("Parsed output as width = %u, height = %u at (%u, %u)%s\n", + width, height, x, y, primary ? " (primary)" : ""); + Output *new_output = get_screen_at(x, y); if (new_output != NULL) { DLOG("Re-used old output %p\n", new_output); @@ -67,10 +77,7 @@ void fake_outputs_init(const char *output_spec) { init_ws_for_output(new_output, output_get_content(new_output->con)); num_screens++; } - - walk += chars_consumed; - if (*walk == ',') - walk++; + new_output->primary = primary; } if (num_screens == 0) { From 8144a0f480355c0cb134223d704345d67bd2b31d Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Tue, 19 Sep 2017 14:46:27 +0000 Subject: [PATCH 093/123] Do not canonicalize special output names canonicalize_output_name allowed the "primary" special output name to be canonicalized, thus converting it to the name of whatever output was the primary output at the time. This caused settings (specifically, i3bar output and tray_output settings) to be stored as specific output names, instead of the intended special names whose referred output may change as the system's configuration (i.e. current primary output) changes. Add a check to canonicalize_output_name to return the name as-is if it is the special name "primary". --- src/ipc.c | 4 +++ testcases/t/538-i3bar-primary-output.t | 40 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 testcases/t/538-i3bar-primary-output.t diff --git a/src/ipc.c b/src/ipc.c index dc953adc..759665fe 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -580,6 +580,10 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) { } static char *canonicalize_output_name(char *name) { + /* Do not canonicalize special output names. */ + if (strcasecmp(name, "primary") == 0) { + return name; + } Output *output = get_output_by_name(name, false); return output ? output_primary_name(output) : name; } diff --git a/testcases/t/538-i3bar-primary-output.t b/testcases/t/538-i3bar-primary-output.t new file mode 100644 index 00000000..67fd1b6c --- /dev/null +++ b/testcases/t/538-i3bar-primary-output.t @@ -0,0 +1,40 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests that i3bars configured to use the primary output do not have +# their output names canonicalized to something other than "primary". +# Ticket: #2948 +# Bug still in: 4.14-93-ga3a7d04a +use i3test i3_config => <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}, [ "primary" ], 'bar_config output is primary'); + +done_testing; From 683e33199d7905786ddc83a9fcf33d0b722c2e50 Mon Sep 17 00:00:00 2001 From: "Martin T. H. Sandsmark" Date: Tue, 27 Dec 2016 19:00:09 +0100 Subject: [PATCH 094/123] Don't put new floating windows on top unless they're focused --- src/floating.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index f2994339..f0499e60 100644 --- a/src/floating.c +++ b/src/floating.c @@ -196,7 +196,11 @@ void floating_enable(Con *con, bool automatic) { /* We insert nc already, even though its rect is not yet calculated. This * is necessary because otherwise the workspace might be empty (and get * closed in tree_close_internal()) even though it’s not. */ - TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); + if (set_focus) { + TAILQ_INSERT_TAIL(&(ws->floating_head), nc, floating_windows); + } else { + TAILQ_INSERT_HEAD(&(ws->floating_head), nc, floating_windows); + } TAILQ_INSERT_TAIL(&(ws->focus_head), nc, focused); /* check if the parent container is empty and close it if so */ From 8653bfe8d38c206bda9ffd50fce28f8409bcc4d9 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 22 Sep 2017 22:00:06 +0300 Subject: [PATCH 095/123] Raise floating window to top when it gets focus Applied for: 1. '[...] focus' for a floating container raises it to the top. 2. Focusing a window through a focus event raises it to the top. Fixes #2572 --- src/click.c | 2 -- src/con.c | 7 +++++++ src/handlers.c | 8 +++++--- testcases/t/135-floating-focus.t | 24 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/click.c b/src/click.c index e5cdc8b2..78af8a03 100644 --- a/src/click.c +++ b/src/click.c @@ -262,8 +262,6 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod * We will skip handling events on floating cons in fullscreen mode */ Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL); if (floatingcon != NULL && fs != con) { - floating_raise_con(floatingcon); - /* 4: floating_modifier plus left mouse button drags */ if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) { floating_drag_window(floatingcon, event); diff --git a/src/con.c b/src/con.c index df115230..2e22619f 100644 --- a/src/con.c +++ b/src/con.c @@ -243,6 +243,13 @@ void con_focus(Con *con) { workspace_update_urgent_flag(con_get_workspace(con)); ipc_send_window_event("urgent", con); } + + /* Focusing a container with a floating parent should raise it to the top. Since + * con_focus is called recursively for each parent we don't need to use + * con_inside_floating(). */ + if (con->type == CT_FLOATING_CON) { + floating_raise_con(con); + } } /* diff --git a/src/handlers.c b/src/handlers.c index 8d500fd9..1da42ec2 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1221,7 +1221,9 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { return; } - if (focused_id == event->event) { + /* Floating windows should be refocused to ensure that they are on top of + * other windows. */ + if (focused_id == event->event && !con_inside_floating(con)) { DLOG("focus matches the currently focused window, not doing anything\n"); return; } @@ -1232,7 +1234,7 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { return; } - DLOG("focus is different, updating decorations\n"); + DLOG("focus is different / refocusing floating window: updating decorations\n"); /* Get the currently focused workspace to check if the focus change also * involves changing workspaces. If so, we need to call workspace_show() to @@ -1244,7 +1246,7 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { con_focus(con); /* We update focused_id because we don’t need to set focus again */ focused_id = event->event; - x_push_changes(croot); + tree_render(); return; } diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t index f23dabae..f21c0486 100644 --- a/testcases/t/135-floating-focus.t +++ b/testcases/t/135-floating-focus.t @@ -216,4 +216,28 @@ cmd 'focus child'; is($x->input_focus, $floating->id, 'floating window focused'); +############################################################################# +# 8: verify that focusing a floating window raises it to the top. +# This test can't verify that the floating container is visually on top, just +# that it is placed on the tail of the floating_head. +# See issue: 2572 +############################################################################# + +$tmp = fresh_workspace; + +$first = open_floating_window; +$second = open_floating_window; + +is($x->input_focus, $second->id, 'second floating window focused'); +my $ws = get_ws($tmp); +is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $second->id, 'second on top'); +is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $first->id, 'first behind'); + +cmd '[id=' . $first->id . '] focus'; + +is($x->input_focus, $first->id, 'first floating window focused'); +$ws = get_ws($tmp); +is($ws->{floating_nodes}->[1]->{nodes}->[0]->{window}, $first->id, 'first on top'); +is($ws->{floating_nodes}->[0]->{nodes}->[0]->{window}, $second->id, 'second behind'); + done_testing; From 2e83d2193eab403673340c19dd785f2708be12b3 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 22 Sep 2017 19:22:48 +0300 Subject: [PATCH 096/123] Add con_exists function Checks the all_cons queue and returns true if a given con is found. --- include/con.h | 7 +++++++ src/con.c | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/include/con.h b/include/con.h index 6cd1ef3e..674ee61d 100644 --- a/include/con.h +++ b/include/con.h @@ -165,6 +165,13 @@ Con *con_by_window_id(xcb_window_t window); */ Con *con_by_con_id(long target); +/** + * Returns true if the given container (still) exists. + * This can be used, e.g., to make sure a container hasn't been closed in the meantime. + * + */ +bool con_exists(Con *con); + /** * Returns the container with the given frame ID or NULL if no such container * exists. diff --git a/src/con.c b/src/con.c index 2e22619f..fcde4c10 100644 --- a/src/con.c +++ b/src/con.c @@ -604,6 +604,15 @@ Con *con_by_con_id(long target) { return NULL; } +/* + * Returns true if the given container (still) exists. + * This can be used, e.g., to make sure a container hasn't been closed in the meantime. + * + */ +bool con_exists(Con *con) { + return con_by_con_id((long)con) != NULL; +} + /* * Returns the container with the given frame ID or NULL if no such container * exists. From eadc9a84616a78575d4311abb662ef469715ad08 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 22 Sep 2017 19:25:02 +0300 Subject: [PATCH 097/123] Check container existance during drag events This fixes a crash that occurs when disabling floating for a container while it is being moved or resized. @Deiz describes the problem: > It occurs because the command that disables floating runs before the event loop. So, the window is tiled, its floating parent is destroyed, but then a key event is handled which causes the position/size of the now-destroyed parent to be modified. Fixes #1627 --- src/floating.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/floating.c b/src/floating.c index f0499e60..5f46dcf9 100644 --- a/src/floating.c +++ b/src/floating.c @@ -535,6 +535,11 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { /* Drag the window */ drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event); + if (!con_exists(con)) { + DLOG("The container has been closed in the meantime.\n"); + return; + } + /* If the user cancelled, undo the changes. */ if (drag_result == DRAG_REVERT) floating_reposition(con, initial_rect); @@ -646,6 +651,11 @@ void floating_resize_window(Con *con, const bool proportional, drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms); + if (!con_exists(con)) { + DLOG("The container has been closed in the meantime.\n"); + return; + } + /* If the user cancels, undo the resize */ if (drag_result == DRAG_REVERT) floating_reposition(con, initial_rect); @@ -743,12 +753,17 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) { if (last_motion_notify == NULL) return; - dragloop->callback( - dragloop->con, - &(dragloop->old_rect), - last_motion_notify->root_x, - last_motion_notify->root_y, - dragloop->extra); + /* Ensure that we are either dragging the resize handle (con is NULL) or that the + * container still exists. The latter might not be true, e.g., if the window closed + * for any reason while the user was dragging it. */ + if (!dragloop->con || con_exists(dragloop->con)) { + dragloop->callback( + dragloop->con, + &(dragloop->old_rect), + last_motion_notify->root_x, + last_motion_notify->root_y, + dragloop->extra); + } free(last_motion_notify); } From aa0b1f599f25cfe858ebbc7fa80d459bcdb2ae02 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 24 Sep 2017 10:19:07 +0200 Subject: [PATCH 098/123] Replace http:// with https:// where applicable The testcases will be updated automatically in a separate commit. --- .github/CONTRIBUTING.md | 6 +++--- .github/ISSUE_TEMPLATE.md | 2 +- AnyEvent-I3/Makefile.PL | 2 +- AnyEvent-I3/README | 6 +++--- AnyEvent-I3/lib/AnyEvent/I3.pm | 8 ++++---- DEPENDS | 26 +++++++++++++------------- contrib/per-workspace-layout.pl | 2 +- debian/control | 2 +- debian/copyright | 2 +- docs/asciidoc-git.conf | 6 +++--- docs/debugging | 16 ++++++++-------- docs/hacking-howto | 6 +++--- docs/i3-pod2html | 6 +++--- docs/i3bar-protocol | 2 +- docs/layout-saving | 8 ++++---- docs/refcard.html | 2 +- docs/testsuite | 10 +++++----- docs/userguide | 6 +++--- docs/wsbar | 2 +- etc/config | 2 +- etc/config.keycodes | 2 +- i3-dmenu-desktop | 10 +++++----- i3-dump-log/main.c | 2 +- i3bar/src/xcb.c | 2 +- include/ewmh.h | 6 +++--- include/libi3.h | 2 +- include/randr.h | 2 +- include/sd-daemon.h | 4 ++-- libi3/get_exe_path.c | 2 +- man/i3-msg.man | 2 +- man/i3.man | 2 +- man/i3bar.man | 2 +- pseudo-doc.doxygen | 2 +- src/bindings.c | 2 +- src/ewmh.c | 6 +++--- src/handlers.c | 4 ++-- src/main.c | 2 +- src/randr.c | 2 +- src/render.c | 8 ++++---- src/restore_layout.c | 2 +- src/sighandler.c | 2 +- src/tree.c | 2 +- src/window.c | 2 +- src/x.c | 2 +- testcases/lib/StartXServer.pm | 2 +- testcases/lib/StatusLine.pm | 2 +- testcases/lib/i3test.pm.in | 4 ++-- testcases/lib/i3test/XTEST.pm | 2 +- testcases/new-test | 6 +++--- travis/docs.sh | 4 ++-- 50 files changed, 108 insertions(+), 108 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c19ac81e..84644060 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,7 +6,7 @@ Note that bug reports and feature requests for related projects should be filed ## i3 bug reports and feature requests -1. Read the [debugging instructions](http://i3wm.org/docs/debugging.html). +1. Read the [debugging instructions](https://i3wm.org/docs/debugging.html). 2. Make sure you include a link to your logfile in your report (section 3). 3. Make sure you include the i3 version number in your report (section 1). 4. Please be aware that we cannot support compatibility issues with @@ -25,11 +25,11 @@ Note that bug reports and feature requests for related projects should be filed ”feature request” or ”enhancement” in its title. * Use the `next` branch for developing and sending your pull request. * Use `clang-format` to format your code. -* Run the [testsuite](http://i3wm.org/docs/testsuite.html) +* Run the [testsuite](https://i3wm.org/docs/testsuite.html) ## Finding something to do * Find a [reproducible bug](https://github.com/i3/i3/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Areproducible+label%3Abug+) from the issue tracker. These issues have been reviewed and confirmed by a project contributor. * Find an [accepted enhancement](https://github.com/i3/i3/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3Aaccepted+label%3Aenhancement) from the issue tracker. These have been approved and are ok to start working on. -There's a very good [overview of the codebase](http://i3wm.org/docs/hacking-howto.html) available to get you started. +There's a very good [overview of the codebase](https://i3wm.org/docs/hacking-howto.html) available to get you started. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index d2254676..a9cfbd47 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ Output of `i3 --moreversion 2>&- || i3 --version`: _REPLACE: i3 version output_ -URL to a logfile as per http://i3wm.org/docs/debugging.html: +URL to a logfile as per https://i3wm.org/docs/debugging.html: _REPLACE: URL to logfile_ diff --git a/AnyEvent-I3/Makefile.PL b/AnyEvent-I3/Makefile.PL index 29a442f4..cd727197 100644 --- a/AnyEvent-I3/Makefile.PL +++ b/AnyEvent-I3/Makefile.PL @@ -23,7 +23,7 @@ my %meta = ( web => 'https://github.com/i3/i3/issues', }, homepage => 'https://i3wm.org/', - license => ['http://dev.perl.org/licenses'], + license => ['https://dev.perl.org/licenses'], }, ); diff --git a/AnyEvent-I3/README b/AnyEvent-I3/README index 4658ba16..fe9fdb74 100644 --- a/AnyEvent-I3/README +++ b/AnyEvent-I3/README @@ -23,10 +23,10 @@ perldoc command. You can also look for information at: RT, CPAN's request tracker - http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3 + https://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3 The i3 window manager website - http://i3.zekjur.net/ + https://i3wm.org LICENSE AND COPYRIGHT @@ -37,4 +37,4 @@ This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. -See http://dev.perl.org/licenses/ for more information. +See https://dev.perl.org/licenses/ for more information. diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 134b1eb4..53ad8215 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -543,7 +543,7 @@ Michael Stapelberg, C<< >> Please report any bugs or feature requests to C, or through the web interface at -L. I will be +L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. @@ -559,11 +559,11 @@ You can also look for information at: =item * RT: CPAN's request tracker -L +L =item * The i3 window manager website -L +L =back @@ -579,7 +579,7 @@ This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. -See http://dev.perl.org/licenses/ for more information. +See https://dev.perl.org/licenses/ for more information. =cut diff --git a/DEPENDS b/DEPENDS index 167827ff..1e26afa2 100644 --- a/DEPENDS +++ b/DEPENDS @@ -7,29 +7,29 @@ ┌──────────────┬────────┬────────┬───────────────────────────────────────────────────────────┐ │ dependency │ min. │ lkgv │ URL │ ├──────────────┼────────┼────────┼───────────────────────────────────────────────────────────┤ -│ pkg-config │ 0.25 │ 0.29 │ http://pkgconfig.freedesktop.org/ │ -│ libxcb │ 1.1.93 │ 1.12 │ http://xcb.freedesktop.org/dist/ │ -│ xcb-util │ 0.3.3 │ 0.4.1 │ http://xcb.freedesktop.org/dist/ │ -│ xkbcommon │ 0.4.0 │ 0.6.1 │ http://xkbcommon.org/ │ -│ xkbcommon-x11│ 0.4.0 │ 0.6.1 │ http://xkbcommon.org/ │ -│ util-cursor³⁴│ 0.0.99 │ 0.1.3 │ http://xcb.freedesktop.org/dist/ │ -│ util-wm⁴ │ 0.3.8 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ -│ util-keysyms⁴│ 0.3.8 │ 0.4.0 │ http://xcb.freedesktop.org/dist/ │ +│ pkg-config │ 0.25 │ 0.29 │ https://pkgconfig.freedesktop.org/ │ +│ libxcb │ 1.1.93 │ 1.12 │ https://xcb.freedesktop.org/dist/ │ +│ xcb-util │ 0.3.3 │ 0.4.1 │ https://xcb.freedesktop.org/dist/ │ +│ xkbcommon │ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │ +│ xkbcommon-x11│ 0.4.0 │ 0.6.1 │ https://xkbcommon.org/ │ +│ util-cursor³⁴│ 0.0.99 │ 0.1.3 │ https://xcb.freedesktop.org/dist/ │ +│ util-wm⁴ │ 0.3.8 │ 0.3.8 │ https://xcb.freedesktop.org/dist/ │ +│ util-keysyms⁴│ 0.3.8 │ 0.4.0 │ https://xcb.freedesktop.org/dist/ │ │ util-xrm⁴ │ 1.0.0 │ 1.0.0 │ https://github.com/Airblader/xcb-util-xrm │ │ libev │ 4.0 │ 4.19 │ http://libev.schmorp.de/ │ -│ yajl │ 2.0.1 │ 2.1.0 │ http://lloyd.github.com/yajl/ │ +│ yajl │ 2.0.1 │ 2.1.0 │ https://lloyd.github.com/yajl/ │ │ asciidoc │ 8.3.0 │ 8.6.9 │ http://www.methods.co.nz/asciidoc/ │ │ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │ │ Pod::Simple² │ 3.22 │ 3.22 │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/ │ │ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │ -│ PCRE │ 8.12 │ 8.38 │ http://www.pcre.org/ │ -│ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification │ +│ PCRE │ 8.12 │ 8.38 │ https://www.pcre.org/ │ +│ libsn¹ │ 0.10 │ 0.12 │ https://freedesktop.org/wiki/Software/startup-notification │ │ pango │ 1.30.0 | 1.40.1 │ http://www.pango.org/ │ -│ cairo │ 1.14.4 │ 1.14.6 │ http://cairographics.org/ │ +│ cairo │ 1.14.4 │ 1.14.6 │ https://cairographics.org/ │ └──────────────┴────────┴────────┴───────────────────────────────────────────────────────────┘ ¹ libsn = libstartup-notification ² Pod::Simple is a Perl module required for converting the testsuite - documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple + documentation to HTML. See https://michael.stapelberg.de/cpan/#Pod::Simple ³ xcb-util-cursor, to be precise. ⁴ Depending on your distribution, this might be considered part of xcb-util. diff --git a/contrib/per-workspace-layout.pl b/contrib/per-workspace-layout.pl index 9304b6fd..48590456 100644 --- a/contrib/per-workspace-layout.pl +++ b/contrib/per-workspace-layout.pl @@ -1,7 +1,7 @@ #!/usr/bin/env perl # vim:ts=4:sw=4:expandtab # © 2012 Michael Stapelberg -# Licensed under BSD license, see http://code.i3wm.org/i3/tree/LICENSE +# Licensed under BSD license, see https://github.com/i3/i3/blob/next/LICENSE # # Append this line to your i3 config file: # exec_always ~/per-workspace-layout.pl diff --git a/debian/control b/debian/control index 76a84a5a..46e35dfe 100644 --- a/debian/control +++ b/debian/control @@ -27,7 +27,7 @@ Build-Depends: debhelper (>= 9), libpango1.0-dev, libpod-simple-perl Standards-Version: 3.9.8 -Homepage: http://i3wm.org/ +Homepage: https://i3wm.org/ Package: i3 Architecture: any diff --git a/debian/copyright b/debian/copyright index f86d850f..f80edad8 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: i3 Upstream-Contact: Michael Stapelberg Source: https://i3wm.org/ diff --git a/docs/asciidoc-git.conf b/docs/asciidoc-git.conf index 36bdb6f5..08f28a5d 100644 --- a/docs/asciidoc-git.conf +++ b/docs/asciidoc-git.conf @@ -520,8 +520,8 @@ cellspacing="0" cellpadding="4"> [header] - + "https://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> + @@ -647,7 +647,7 @@ endif::doctype-manpage[] {disable-javascript%

} diff --git a/docs/debugging b/docs/debugging index 7be7c8e5..07bc13a0 100644 --- a/docs/debugging +++ b/docs/debugging @@ -32,8 +32,8 @@ if you can. 4.7-85-g9c15b95 (development version):: Your version is 85 commits newer than 4.7, and the git revision of your -version is +9c15b95+. Go to http://code.i3wm.org/i3/commit/?h=next and see if -the line "commit" starts with the same revision. If so, you are using the +version is +9c15b95+. Go to https://github.com/i3/i3/commits/next and see if +the most recent commit starts with the same revision. If so, you are using the latest version. Development versions of i3 have logging enabled by default and are compiled @@ -109,9 +109,9 @@ No matter whether i3 misbehaved in some way without crashing or whether it just crashed, the logfile provides all information necessary to debug the problem. To upload a compressed version of the logfile (for a bugreport), use: ------------------------------------------------------------------------------- -DISPLAY=:0 i3-dump-log | bzip2 -c | curl --data-binary @- http://logs.i3wm.org ------------------------------------------------------------------------------- +------------------------------------------------------------------------------- +DISPLAY=:0 i3-dump-log | bzip2 -c | curl --data-binary @- https://logs.i3wm.org +------------------------------------------------------------------------------- This command does not depend on i3 (it also works while i3 displays the crash dialog), but it requires a working X11 connection. @@ -154,9 +154,9 @@ you found the section which clearly highlights the problem, additional information might be necessary to completely diagnose the problem. When debugging with us in IRC, be prepared to use a so called nopaste service -such as http://nopaste.info or http://pastebin.com because pasting large -amounts of text in IRC sometimes leads to incomplete lines (servers have line -length limitations) or flood kicks. +such as https://pastebin.com because pasting large amounts of text in IRC +sometimes leads to incomplete lines (servers have line length limitations) or +flood kicks. == Debugging i3bar diff --git a/docs/hacking-howto b/docs/hacking-howto index d585c2d7..2ca44a5f 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -52,8 +52,8 @@ Here’s a memory refresher: == Using git / sending patches For a short introduction into using git, see -http://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy -or, for more documentation, see http://git-scm.com/documentation +https://web.archive.org/web/20121024222556/http://www.spheredev.org/wiki/Git_for_the_lazy +or, for more documentation, see https://git-scm.com/documentation Please talk to us before working on new features to see whether they will be accepted. A good way for this is to open an issue and asking for opinions on it. @@ -156,7 +156,7 @@ workspace, the split container we are talking about is the workspace. To get an impression of how different layouts are represented, just play around and look at the data structures -- they are exposed as a JSON hash. See -http://i3wm.org/docs/ipc.html#_tree_reply for documentation on that and an +https://i3wm.org/docs/ipc.html#_tree_reply for documentation on that and an example. == Files diff --git a/docs/i3-pod2html b/docs/i3-pod2html index bda7e8d7..bd797fcb 100755 --- a/docs/i3-pod2html +++ b/docs/i3-pod2html @@ -31,7 +31,7 @@ $parser->html_header_before_title( - +