From 94228fd902bf62050570c98ae9418380a508393f Mon Sep 17 00:00:00 2001 From: Konst Mayer Date: Tue, 25 Jun 2019 01:07:36 +0700 Subject: [PATCH 01/76] Add setting for minimal width of workspace buttons --- docs/userguide | 29 +++++++++++++ i3bar/include/configuration.h | 1 + i3bar/include/mode.h | 2 +- i3bar/src/config.c | 6 +++ i3bar/src/mode.c | 2 +- i3bar/src/xcb.c | 77 ++++++++++++++++----------------- include/config_directives.h | 1 + include/configuration.h | 3 ++ parser-specs/config.spec | 11 +++++ src/config_directives.c | 4 ++ src/ipc.c | 3 ++ testcases/t/177-bar-config.t | 3 ++ testcases/t/201-config-parser.t | 2 +- 13 files changed, 101 insertions(+), 43 deletions(-) diff --git a/docs/userguide b/docs/userguide index 0fda7e80..ea65bcd1 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1625,6 +1625,35 @@ bar { } ------------------------ +=== Minimal width for workspace buttons + +By default, the width a workspace button is determined by the width of the text +showing the workspace name. If the name is too short (say, one letter), then the +workspace button may look too small. + +This option specifies the minimum width for workspace buttons. If the name of +a workspace is too short to cover the button, an additional padding is added on +both sides of the button so that the text is centered. + +The default value of zero means that no additional padding is added. + +The setting also applies to the current binding mode indicator. + +Note that the specified pixels refer to logical pixels, which may translate +into more pixels on HiDPI displays. + +*Syntax*: +------------------------ +workspace_min_width [px] +------------------------ + +*Example*: +------------------------ +bar { + workspace_min_width 40 +} +------------------------ + === Strip workspace numbers/name Specifies whether workspace numbers should be displayed within the workspace diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index 3d875e5d..aeb7db19 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -52,6 +52,7 @@ typedef struct config_t { struct xcb_color_strings_t colors; bool disable_binding_mode_indicator; bool disable_ws; + int ws_min_width; bool strip_ws_numbers; bool strip_ws_name; char *bar_id; diff --git a/i3bar/include/mode.h b/i3bar/include/mode.h index 5c87d904..e8e4296d 100644 --- a/i3bar/include/mode.h +++ b/i3bar/include/mode.h @@ -18,7 +18,7 @@ /* Name of current binding mode and its render width */ struct mode { i3String *name; - int width; + int name_width; }; typedef struct mode mode; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index c2325629..26004071 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -345,6 +345,12 @@ static int config_integer_cb(void *params_, long long val) { return 1; } + if (!strcmp(cur_key, "workspace_min_width")) { + DLOG("workspace_min_width = %lld\n", val); + config.ws_min_width = val; + return 1; + } + return 0; } diff --git a/i3bar/src/mode.c b/i3bar/src/mode.c index 17e7b97e..97087ce0 100644 --- a/i3bar/src/mode.c +++ b/i3bar/src/mode.c @@ -81,7 +81,7 @@ static int mode_end_map_cb(void *params_) { params->mode->name = i3string_from_utf8(params->name); i3string_set_markup(params->mode->name, params->pango_markup); /* Save its rendered width */ - params->mode->width = predict_text_width(params->mode->name); + params->mode->name_width = predict_text_width(params->mode->name); DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name)); FREE(params->cur_key); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 412981bc..da95a5dc 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -499,6 +499,15 @@ static void child_handle_button(xcb_button_press_event_t *event, i3_output *outp } } +/* + * Predict the width of a workspace button or the current binding mode indicator. + * + */ +static int predict_button_width(int name_width) { + return MAX(name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), + logical_px(config.ws_min_width)); +} + /* * Handle a button press event (i.e. a mouse click on one of our bars). * We determine, whether the click occurred on a workspace button or if the scroll- @@ -530,7 +539,7 @@ static void handle_button(xcb_button_press_event_t *event) { i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk; TAILQ_FOREACH(ws_walk, walk->workspaces, tailq) { - int w = 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width; + int w = predict_button_width(ws_walk->name_width); if (x >= workspace_width && x <= workspace_width + w) clicked_ws = ws_walk; if (ws_walk->visible) @@ -1909,6 +1918,25 @@ void reconfig_windows(bool redraw_bars) { } } +/* + * Draw the button for a workspace or the current binding mode indicator. + * + */ +static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color, color_t border_color, + int x, int width, int text_width, i3String *text) { + int height = font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1); + + /* Draw the border of the button. */ + draw_util_rectangle(surface, border_color, x, logical_px(1), width, height); + + /* Draw the inside of the button. */ + draw_util_rectangle(surface, bg_color, x + logical_px(1), 2 * logical_px(1), + width - 2 * logical_px(1), height - 2 * logical_px(1)); + + draw_util_text(text, surface, fg_color, bg_color, x + (width - text_width) / 2, + logical_px(ws_voff_px), text_width); +} + /* * Render the bars, with buttons and statusline * @@ -1964,26 +1992,11 @@ void draw_bars(bool unhide) { unhide = true; } - /* Draw the border of the button. */ - draw_util_rectangle(&(outputs_walk->buffer), border_color, - workspace_width, - logical_px(1), - ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), - font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); + int w = predict_button_width(ws_walk->name_width); + draw_button(&(outputs_walk->buffer), fg_color, bg_color, border_color, + workspace_width, w, ws_walk->name_width, ws_walk->name); - /* Draw the inside of the button. */ - draw_util_rectangle(&(outputs_walk->buffer), bg_color, - workspace_width + logical_px(1), - 2 * logical_px(1), - ws_walk->name_width + 2 * logical_px(ws_hoff_px), - font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); - - draw_util_text(ws_walk->name, &(outputs_walk->buffer), fg_color, bg_color, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - ws_walk->name_width); - - workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + ws_walk->name_width; + workspace_width += w; if (TAILQ_NEXT(ws_walk, tailq) != NULL) workspace_width += logical_px(ws_spacing_px); } @@ -1992,28 +2005,12 @@ void draw_bars(bool unhide) { if (binding.name && !config.disable_binding_mode_indicator) { workspace_width += logical_px(ws_spacing_px); - color_t fg_color = colors.binding_mode_fg; - color_t bg_color = colors.binding_mode_bg; - - draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border, - workspace_width, - logical_px(1), - binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), - font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); - - draw_util_rectangle(&(outputs_walk->buffer), bg_color, - workspace_width + logical_px(1), - 2 * logical_px(1), - binding.width + 2 * logical_px(ws_hoff_px), - font.height + 2 * logical_px(ws_voff_px) - 4 * logical_px(1)); - - draw_util_text(binding.name, &(outputs_walk->buffer), fg_color, bg_color, - workspace_width + logical_px(ws_hoff_px) + logical_px(1), - logical_px(ws_voff_px), - binding.width); + int w = predict_button_width(binding.name_width); + draw_button(&(outputs_walk->buffer), colors.binding_mode_fg, colors.binding_mode_bg, + colors.binding_mode_border, workspace_width, w, binding.name_width, binding.name); unhide = true; - workspace_width += 2 * logical_px(ws_hoff_px) + 2 * logical_px(1) + binding.width; + workspace_width += w; } if (!TAILQ_EMPTY(&statusline_head)) { diff --git a/include/config_directives.h b/include/config_directives.h index 72b59ea2..06fbd3b0 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -97,6 +97,7 @@ CFGFUN(bar_color_single, const char *colorclass, const char *color); CFGFUN(bar_status_command, const char *command); CFGFUN(bar_binding_mode_indicator, const char *value); CFGFUN(bar_workspace_buttons, const char *value); +CFGFUN(bar_workspace_min_width, const long width); CFGFUN(bar_strip_workspace_numbers, const char *value); CFGFUN(bar_strip_workspace_name, const char *value); CFGFUN(bar_start); diff --git a/include/configuration.h b/include/configuration.h index 872f11c8..d7c2ab8b 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -325,6 +325,9 @@ struct Barconfig { * zero. */ bool hide_workspace_buttons; + /** The minimal width for workspace buttons. */ + int workspace_min_width; + /** Strip workspace numbers? Configuration option is * 'strip_workspace_numbers yes'. */ bool strip_workspace_numbers; diff --git a/parser-specs/config.spec b/parser-specs/config.spec index a256d8b0..d5b7e063 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -468,6 +468,7 @@ state BAR: 'separator_symbol' -> BAR_SEPARATOR_SYMBOL 'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR 'workspace_buttons' -> BAR_WORKSPACE_BUTTONS + 'workspace_min_width' -> BAR_WORKSPACE_MIN_WIDTH 'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS 'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME 'verbose' -> BAR_VERBOSE @@ -572,6 +573,16 @@ state BAR_WORKSPACE_BUTTONS: value = word -> call cfg_bar_workspace_buttons($value); BAR +state BAR_WORKSPACE_MIN_WIDTH: + width = number + -> BAR_WORKSPACE_MIN_WIDTH_PX + +state BAR_WORKSPACE_MIN_WIDTH_PX: + 'px' + -> + end + -> call cfg_bar_workspace_min_width(&width); BAR + state BAR_STRIP_WORKSPACE_NUMBERS: value = word -> call cfg_bar_strip_workspace_numbers($value); BAR diff --git a/src/config_directives.c b/src/config_directives.c index fb3fba41..f647fb4d 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -646,6 +646,10 @@ CFGFUN(bar_workspace_buttons, const char *value) { current_bar->hide_workspace_buttons = !eval_boolstr(value); } +CFGFUN(bar_workspace_min_width, const long width) { + current_bar->workspace_min_width = width; +} + CFGFUN(bar_strip_workspace_numbers, const char *value) { current_bar->strip_workspace_numbers = eval_boolstr(value); } diff --git a/src/ipc.c b/src/ipc.c index e548b5a6..c8a3356f 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -780,6 +780,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { ystr("workspace_buttons"); y(bool, !config->hide_workspace_buttons); + ystr("workspace_min_width"); + y(integer, config->workspace_min_width); + ystr("strip_workspace_numbers"); y(bool, config->strip_workspace_numbers); diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index f35b7120..1a59c6b7 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -63,6 +63,7 @@ my $bar_config = $i3->get_bar_config($bar_id)->recv; is($bar_config->{status_command}, 'i3status --foo', 'status_command correct'); ok(!$bar_config->{verbose}, 'verbose off by default'); ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default'); +is($bar_config->{workspace_min_width}, 0, 'workspace_min_width ok'); ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled per default'); is($bar_config->{mode}, 'dock', 'dock mode by default'); is($bar_config->{position}, 'bottom', 'position bottom by default'); @@ -102,6 +103,7 @@ bar { mode dock font Terminus workspace_buttons no + workspace_min_width 30 binding_mode_indicator no verbose yes socket_path /tmp/foobar @@ -134,6 +136,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv; is($bar_config->{status_command}, 'i3status --bar', 'status_command correct'); ok($bar_config->{verbose}, 'verbose on'); ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled'); +is($bar_config->{workspace_min_width}, 30, 'workspace_min_width ok'); ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 65997bc7..a8d45325 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -733,7 +733,7 @@ EOT $expected = <<'EOT'; cfg_bar_start() cfg_bar_output(LVDS-1) -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: bar { ERROR: CONFIG: Line 2: output LVDS-1 From ecffbed45fcb75ab0572a24b53f4ce42acdb44fd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Jul 2019 08:58:39 -0700 Subject: [PATCH 02/76] default config: use workspace number, not just workspace This is strictly better: if the configured name does not match the current name, the correct workspace will still be used. When creating a new workspace, the configured name is still used. --- etc/config | 41 ++++++++++++++++++++--------------------- etc/config.keycodes | 40 ++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/etc/config b/etc/config index f6f2f9db..49e79060 100644 --- a/etc/config +++ b/etc/config @@ -117,30 +117,29 @@ set $ws8 "8" set $ws9 "9" set $ws10 "10" - # switch to workspace -bindsym Mod1+1 workspace $ws1 -bindsym Mod1+2 workspace $ws2 -bindsym Mod1+3 workspace $ws3 -bindsym Mod1+4 workspace $ws4 -bindsym Mod1+5 workspace $ws5 -bindsym Mod1+6 workspace $ws6 -bindsym Mod1+7 workspace $ws7 -bindsym Mod1+8 workspace $ws8 -bindsym Mod1+9 workspace $ws9 -bindsym Mod1+0 workspace $ws10 +bindsym Mod1+1 workspace number $ws1 +bindsym Mod1+2 workspace number $ws2 +bindsym Mod1+3 workspace number $ws3 +bindsym Mod1+4 workspace number $ws4 +bindsym Mod1+5 workspace number $ws5 +bindsym Mod1+6 workspace number $ws6 +bindsym Mod1+7 workspace number $ws7 +bindsym Mod1+8 workspace number $ws8 +bindsym Mod1+9 workspace number $ws9 +bindsym Mod1+0 workspace number $ws10 # move focused container to workspace -bindsym Mod1+Shift+1 move container to workspace $ws1 -bindsym Mod1+Shift+2 move container to workspace $ws2 -bindsym Mod1+Shift+3 move container to workspace $ws3 -bindsym Mod1+Shift+4 move container to workspace $ws4 -bindsym Mod1+Shift+5 move container to workspace $ws5 -bindsym Mod1+Shift+6 move container to workspace $ws6 -bindsym Mod1+Shift+7 move container to workspace $ws7 -bindsym Mod1+Shift+8 move container to workspace $ws8 -bindsym Mod1+Shift+9 move container to workspace $ws9 -bindsym Mod1+Shift+0 move container to workspace $ws10 +bindsym Mod1+Shift+1 move container to workspace number $ws1 +bindsym Mod1+Shift+2 move container to workspace number $ws2 +bindsym Mod1+Shift+3 move container to workspace number $ws3 +bindsym Mod1+Shift+4 move container to workspace number $ws4 +bindsym Mod1+Shift+5 move container to workspace number $ws5 +bindsym Mod1+Shift+6 move container to workspace number $ws6 +bindsym Mod1+Shift+7 move container to workspace number $ws7 +bindsym Mod1+Shift+8 move container to workspace number $ws8 +bindsym Mod1+Shift+9 move container to workspace number $ws9 +bindsym Mod1+Shift+0 move container to workspace number $ws10 # reload the configuration file bindsym Mod1+Shift+c reload diff --git a/etc/config.keycodes b/etc/config.keycodes index 6fc19426..45201b25 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -105,28 +105,28 @@ set $ws9 "9" set $ws10 "10" # switch to workspace -bindcode $mod+10 workspace $ws1 -bindcode $mod+11 workspace $ws2 -bindcode $mod+12 workspace $ws3 -bindcode $mod+13 workspace $ws4 -bindcode $mod+14 workspace $ws5 -bindcode $mod+15 workspace $ws6 -bindcode $mod+16 workspace $ws7 -bindcode $mod+17 workspace $ws8 -bindcode $mod+18 workspace $ws9 -bindcode $mod+19 workspace $ws10 +bindcode $mod+10 workspace number $ws1 +bindcode $mod+11 workspace number $ws2 +bindcode $mod+12 workspace number $ws3 +bindcode $mod+13 workspace number $ws4 +bindcode $mod+14 workspace number $ws5 +bindcode $mod+15 workspace number $ws6 +bindcode $mod+16 workspace number $ws7 +bindcode $mod+17 workspace number $ws8 +bindcode $mod+18 workspace number $ws9 +bindcode $mod+19 workspace number $ws10 # move focused container to workspace -bindcode $mod+Shift+10 move container to workspace $ws1 -bindcode $mod+Shift+11 move container to workspace $ws2 -bindcode $mod+Shift+12 move container to workspace $ws3 -bindcode $mod+Shift+13 move container to workspace $ws4 -bindcode $mod+Shift+14 move container to workspace $ws5 -bindcode $mod+Shift+15 move container to workspace $ws6 -bindcode $mod+Shift+16 move container to workspace $ws7 -bindcode $mod+Shift+17 move container to workspace $ws8 -bindcode $mod+Shift+18 move container to workspace $ws9 -bindcode $mod+Shift+19 move container to workspace $ws10 +bindcode $mod+Shift+10 move container to workspace number $ws1 +bindcode $mod+Shift+11 move container to workspace number $ws2 +bindcode $mod+Shift+12 move container to workspace number $ws3 +bindcode $mod+Shift+13 move container to workspace number $ws4 +bindcode $mod+Shift+14 move container to workspace number $ws5 +bindcode $mod+Shift+15 move container to workspace number $ws6 +bindcode $mod+Shift+16 move container to workspace number $ws7 +bindcode $mod+Shift+17 move container to workspace number $ws8 +bindcode $mod+Shift+18 move container to workspace number $ws9 +bindcode $mod+Shift+19 move container to workspace number $ws10 # reload the configuration file bindcode $mod+Shift+54 reload From c36311f614f8dfdee41bc74d78ff211aa3f0c40b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 3 Aug 2019 15:22:06 +0200 Subject: [PATCH 03/76] debian: update changelog --- debian/changelog | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index f9aa8c9e..ec6f19c7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,20 @@ -i3-wm (4.16.1-1) unstable; urgency=medium +i3-wm (4.17.1-1) unstable; urgency=medium * UNRELEASED - -- Michael Stapelberg Sun, 04 Nov 2018 14:47:25 +0100 + -- Michael Stapelberg Sat, 03 Aug 2019 15:14:28 +0200 + +i3-wm (4.17-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Sat, 03 Aug 2019 15:14:28 +0200 + +i3-wm (4.16.1-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Sun, 27 Jan 2019 16:45:11 +0100 i3-wm (4.16-1) unstable; urgency=medium From 840d9202d1ec4268e92f0341b692c71c710fae78 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 3 Aug 2019 15:29:04 +0200 Subject: [PATCH 04/76] release.sh changes for v4.17 --- release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release.sh b/release.sh index 97d91868..095bc50b 100755 --- a/release.sh +++ b/release.sh @@ -1,8 +1,8 @@ #!/bin/zsh # This script is used to prepare a new release of i3. -export RELEASE_VERSION="4.16" -export PREVIOUS_VERSION="4.15" +export RELEASE_VERSION="4.17" +export PREVIOUS_VERSION="4.16" export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] From 3b88e41dd86468b21c202a5858a9362ca747a0b4 Mon Sep 17 00:00:00 2001 From: Antoine Date: Wed, 7 Aug 2019 22:43:01 -0700 Subject: [PATCH 05/76] =?UTF-8?q?move=20workspace=20to=20output:=20don?= =?UTF-8?q?=E2=80=99t=20create=20duplicatively=20numbered=20workspaces=20(?= =?UTF-8?q?#3746)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #3745 --- src/workspace.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 59705798..4f618751 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -983,11 +983,15 @@ void workspace_move_to_output(Con *ws, Output *output) { bool used_assignment = false; struct Workspace_Assignment *assignment; TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + bool attached; + int num; if (!output_triggers_assignment(current_output, assignment)) { continue; } - /* check if this workspace is already attached to the tree */ - if (get_existing_workspace_by_name(assignment->name) != NULL) { + /* check if this workspace's name or num is already attached to the tree */ + num = ws_name_to_number(assignment->name); + attached = ((num == -1) ? get_existing_workspace_by_name(assignment->name) : get_existing_workspace_by_num(num)) != NULL; + if (attached) { continue; } From 0845d7b264fdd8aa65b0d56c100ed716db06c63a Mon Sep 17 00:00:00 2001 From: izzel <34325651+izzel@users.noreply.github.com> Date: Tue, 13 Aug 2019 02:50:48 -0400 Subject: [PATCH 06/76] Remanage window after property updates (like titles) (#3759) --- docs/layout-saving | 24 ----- include/con.h | 8 ++ include/data.h | 4 + include/manage.h | 7 ++ include/startup.h | 6 ++ include/window.h | 8 +- src/con.c | 58 ++++++++---- src/handlers.c | 16 +++- src/manage.c | 107 ++++++++++++++++++---- src/startup.c | 19 ++++ src/window.c | 20 +--- testcases/t/542-layout-restore-remanage.t | 86 +++++++++++++++++ 12 files changed, 279 insertions(+), 84 deletions(-) create mode 100644 testcases/t/542-layout-restore-remanage.t diff --git a/docs/layout-saving b/docs/layout-saving index f31b5e21..4f0ffccf 100644 --- a/docs/layout-saving +++ b/docs/layout-saving @@ -261,27 +261,3 @@ container: ] } -------------------------------------------------------------------------------- - -=== Placeholders using window title matches don't swallow the window - -If you use the +title+ attribute to match a window and find that it doesn't -work or only works sometimes, the reason might be that the application sets the -title only after making the window visible. This will be especially true for -programs running inside terminal emulators, e.g., +urxvt -e irssi+ when -matching on +title: "irssi"+. - -One way to deal with this is to not rely on the title, but instead use, e.g., -the +instance+ attribute and running the program to set this window instance to -that value: - --------------------------------------------------------------------------------- -# Run irssi via -# urxvt -name "irssi-container" -e irssi - -"swallows": [ - { - "class": "URxvt", - "instance": "irssi-container" - } -] --------------------------------------------------------------------------------- diff --git a/include/con.h b/include/con.h index 09ec7b44..2d843eeb 100644 --- a/include/con.h +++ b/include/con.h @@ -533,3 +533,11 @@ bool con_swap(Con *first, Con *second); * */ uint32_t con_rect_size_in_orientation(Con *con); + +/** + * Merges container specific data that should move with the window (e.g. marks, + * title format, and the window itself) into another container, and closes the + * old container. + * + */ +void con_merge_into(Con *old, Con *new); diff --git a/include/data.h b/include/data.h index c3cada37..4c6ecb8c 100644 --- a/include/data.h +++ b/include/data.h @@ -489,6 +489,10 @@ struct Window { bool shaped; /** The window has a nonrectangular input shape. */ bool input_shaped; + + /* Time when the window became managed. Used to determine whether a window + * should be swallowed after initial management. */ + time_t managed_since; }; /** diff --git a/include/manage.h b/include/manage.h index 55b0a85b..22fbe527 100644 --- a/include/manage.h +++ b/include/manage.h @@ -37,3 +37,10 @@ void restore_geometry(void); void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie, bool needs_to_be_mapped); + +/** + * Remanages a window: performs a swallow check and runs assignments. + * Returns con for the window regardless if it updated. + * + */ +Con *remanage_window(Con *con); diff --git a/include/startup.h b/include/startup.h index feece575..0001a77d 100644 --- a/include/startup.h +++ b/include/startup.h @@ -69,3 +69,9 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, * */ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply); + +/** + * Deletes the startup sequence for a window if it exists. + * + */ +void startup_sequence_delete_by_window(i3Window *win); diff --git a/include/window.h b/include/window.h index b03f9c14..6673e835 100644 --- a/include/window.h +++ b/include/window.h @@ -22,14 +22,14 @@ void window_free(i3Window *win); * given window. * */ -void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); +void window_update_class(i3Window *win, xcb_get_property_reply_t *prop); /** * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given * window. Further updates using window_update_name_legacy will be ignored. * */ -void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); +void window_update_name(i3Window *win, xcb_get_property_reply_t *prop); /** * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not @@ -38,7 +38,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo * window_update_name()). * */ -void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); +void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop); /** * Updates the CLIENT_LEADER (logical parent window). @@ -62,7 +62,7 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop); * Updates the WM_WINDOW_ROLE * */ -void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); +void window_update_role(i3Window *win, xcb_get_property_reply_t *prop); /** * Updates the _NET_WM_WINDOW_TYPE property. diff --git a/src/con.c b/src/con.c index 10afb4cc..52c78d0c 100644 --- a/src/con.c +++ b/src/con.c @@ -1258,34 +1258,17 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* 8. If anything within the container is associated with a startup sequence, * delete it so child windows won't be created on the old workspace. */ - struct Startup_Sequence *sequence; - xcb_get_property_cookie_t cookie; - xcb_get_property_reply_t *startup_id_reply; - if (!con_is_leaf(con)) { Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (!child->window) continue; - - cookie = xcb_get_property(conn, false, child->window->id, - A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); - startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); - - sequence = startup_sequence_get(child->window, startup_id_reply, true); - if (sequence != NULL) - startup_sequence_delete(sequence); + startup_sequence_delete_by_window(child->window); } } if (con->window) { - cookie = xcb_get_property(conn, false, con->window->id, - A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); - startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); - - sequence = startup_sequence_get(con->window, startup_id_reply, true); - if (sequence != NULL) - startup_sequence_delete(sequence); + startup_sequence_delete_by_window(con->window); } /* 9. If the container was marked urgent, move the urgency hint. */ @@ -2401,3 +2384,40 @@ bool con_swap(Con *first, Con *second) { uint32_t con_rect_size_in_orientation(Con *con) { return (con_orientation(con) == HORIZ ? con->rect.width : con->rect.height); } + +/* + * Merges container specific data that should move with the window (e.g. marks, + * title format, and the window itself) into another container, and closes the + * old container. + * + */ +void con_merge_into(Con *old, Con *new) { + new->window = old->window; + old->window = NULL; + + if (old->title_format) { + FREE(new->title_format); + new->title_format = old->title_format; + old->title_format = NULL; + } + + if (old->sticky_group) { + FREE(new->sticky_group); + new->sticky_group = old->sticky_group; + old->sticky_group = NULL; + } + + new->sticky = old->sticky; + + con_set_urgency(new, old->urgent); + + mark_t *mark; + TAILQ_FOREACH(mark, &(old->marks_head), marks) { + TAILQ_INSERT_TAIL(&(new->marks_head), mark, marks); + ipc_send_window_event("mark", new); + } + new->mark_changed = (TAILQ_FIRST(&(old->marks_head)) != NULL); + TAILQ_INIT(&(old->marks_head)); + + tree_close_internal(old, DONT_KILL_WINDOW, false); +} diff --git a/src/handlers.c b/src/handlers.c index ae42b82e..7a334978 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -565,7 +565,9 @@ static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL); - window_update_name(con->window, prop, false); + window_update_name(con->window, prop); + + con = remanage_window(con); x_push_changes(croot); @@ -590,7 +592,9 @@ static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn, char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL); - window_update_name_legacy(con->window, prop, false); + window_update_name_legacy(con->window, prop); + + con = remanage_window(con); x_push_changes(croot); @@ -612,7 +616,9 @@ static bool handle_windowrole_change(void *data, xcb_connection_t *conn, uint8_t if ((con = con_by_window_id(window)) == NULL || con->window == NULL) return false; - window_update_role(con->window, prop, false); + window_update_role(con->window, prop); + + con = remanage_window(con); return true; } @@ -1158,7 +1164,9 @@ static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t stat return false; } - window_update_class(con->window, prop, false); + window_update_class(con->window, prop); + + con = remanage_window(con); return true; } diff --git a/src/manage.c b/src/manage.c index 80faa167..df0cd934 100644 --- a/src/manage.c +++ b/src/manage.c @@ -13,6 +13,34 @@ #include +/* + * Match frame and window depth. This is needed because X will refuse to reparent a + * window whose background is ParentRelative under a window with a different depth. + * + */ +static xcb_window_t _match_depth(i3Window *win, Con *con) { + xcb_window_t old_frame = XCB_NONE; + if (con->depth != win->depth) { + old_frame = con->frame.id; + con->depth = win->depth; + x_con_reframe(con); + } + return old_frame; +} + +/* + * Remove all match criteria, the first swallowed window wins. + * + */ +static void _remove_matches(Con *con) { + while (!TAILQ_EMPTY(&(con->swallow_head))) { + Match *first = TAILQ_FIRST(&(con->swallow_head)); + TAILQ_REMOVE(&(con->swallow_head), first, matches); + match_free(first); + free(first); + } +} + /* * Go through all existing windows (if the window manager is restarted) and manage them * @@ -174,13 +202,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki FREE(buttons); /* update as much information as possible so far (some replies may be NULL) */ - window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true); - window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL), true); - window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL), true); + window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL)); + window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); + window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL)); window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); - window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true); + window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL)); bool urgency_hint; window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint); border_style_t motif_border_style = BS_NORMAL; @@ -341,24 +369,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this to-be-managed window?!\n"); } else { /* Remove remaining criteria, the first swallowed window wins. */ - while (!TAILQ_EMPTY(&(nc->swallow_head))) { - Match *first = TAILQ_FIRST(&(nc->swallow_head)); - TAILQ_REMOVE(&(nc->swallow_head), first, matches); - match_free(first); - free(first); - } + _remove_matches(nc); } } xcb_window_t old_frame = XCB_NONE; if (nc->window != cwindow && nc->window != NULL) { window_free(nc->window); - /* Match frame and window depth. This is needed because X will refuse to reparent a - * window whose background is ParentRelative under a window with a different depth. */ - if (nc->depth != cwindow->depth) { - old_frame = nc->frame.id; - nc->depth = cwindow->depth; - x_con_reframe(nc); - } + old_frame = _match_depth(cwindow, nc); } nc->window = cwindow; x_reinit(nc); @@ -594,6 +611,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } render_con(croot); + cwindow->managed_since = time(NULL); + /* Send an event about window creation */ ipc_send_window_event("new", nc); @@ -670,3 +689,57 @@ geom_out: out: free(attr); } + +/* + * Remanages a window: performs a swallow check and runs assignments. + * Returns con for the window regardless if it updated. + * + */ +Con *remanage_window(Con *con) { + Match *match; + Con *nc = con_for_window(croot, con->window, &match); + if (nc == NULL || nc->window == con->window) { + run_assignments(con->window); + return con; + } + /* Make sure the placeholder that wants to swallow this window didn't spawn + * after the window to follow current behavior: adding a placeholder won't + * swallow windows currently managed. */ + if (nc->window->managed_since > con->window->managed_since) { + run_assignments(con->window); + return con; + } + + if (!restore_kill_placeholder(nc->window->id)) { + DLOG("Uh?! Container without a placeholder, but with a window, has swallowed this managed window?!\n"); + } else { + _remove_matches(nc); + } + window_free(nc->window); + + xcb_window_t old_frame = _match_depth(con->window, nc); + + x_reparent_child(nc, con); + + bool moved_workpaces = (con_get_workspace(nc) != con_get_workspace(con)); + + con_merge_into(con, nc); + + /* Destroy the old frame if we had to reframe the container. This needs to be done + * after rendering in order to prevent the background from flickering in its place. */ + if (old_frame != XCB_NONE) { + xcb_destroy_window(conn, old_frame); + } + + run_assignments(nc->window); + + if (moved_workpaces) { + /* If the window is associated with a startup sequence, delete it so + * child windows won't be created on the old workspace. */ + startup_sequence_delete_by_window(nc->window); + + ewmh_update_wm_desktop(); + } + + return nc; +} diff --git a/src/startup.c b/src/startup.c index c5b7ad5d..45d4673e 100644 --- a/src/startup.c +++ b/src/startup.c @@ -365,3 +365,22 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t * return sequence->workspace; } + +/* + * Deletes the startup sequence for a window if it exists. + * + */ +void startup_sequence_delete_by_window(i3Window *win) { + struct Startup_Sequence *sequence; + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *startup_id_reply; + + cookie = xcb_get_property(conn, false, win->id, A__NET_STARTUP_ID, + XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); + + sequence = startup_sequence_get(win, startup_id_reply, true); + if (sequence != NULL) { + startup_sequence_delete(sequence); + } +} diff --git a/src/window.c b/src/window.c index 8c3ae850..369aaa96 100644 --- a/src/window.c +++ b/src/window.c @@ -26,7 +26,7 @@ void window_free(i3Window *win) { * given window. * */ -void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { +void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("WM_CLASS not set.\n"); FREE(prop); @@ -52,9 +52,6 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef win->class_instance, win->class_class); free(prop); - if (!before_mgmt) { - run_assignments(win); - } } /* @@ -62,7 +59,7 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool bef * window. Further updates using window_update_name_legacy will be ignored. * */ -void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { +void window_update_name(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("_NET_WM_NAME not specified, not changing\n"); FREE(prop); @@ -89,9 +86,6 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo win->uses_net_wm_name = true; free(prop); - if (!before_mgmt) { - run_assignments(win); - } } /* @@ -101,7 +95,7 @@ void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool befo * window_update_name()). * */ -void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { +void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n"); FREE(prop); @@ -134,9 +128,6 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo win->name_x_changed = true; free(prop); - if (!before_mgmt) { - run_assignments(win); - } } /* @@ -218,7 +209,7 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop) * Updates the WM_WINDOW_ROLE * */ -void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { +void window_update_role(i3Window *win, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("WM_WINDOW_ROLE not set.\n"); FREE(prop); @@ -233,9 +224,6 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role); free(prop); - if (!before_mgmt) { - run_assignments(win); - } } /* diff --git a/testcases/t/542-layout-restore-remanage.t b/testcases/t/542-layout-restore-remanage.t new file mode 100644 index 00000000..26b50835 --- /dev/null +++ b/testcases/t/542-layout-restore-remanage.t @@ -0,0 +1,86 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests that swallowing still works after a window gets managed and its property +# updated. +use i3test; +use File::Temp qw(tempfile); +use IO::Handle; +use X11::XCB qw(PROP_MODE_REPLACE); + +sub change_window_title { + my ($window, $title, $length) = @_; + my $atomname = $x->atom(name => '_NET_WM_NAME'); + my $atomtype = $x->atom(name => 'UTF8_STRING'); + $length ||= length($title) + 1; + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 8, + $length, + $title + ); + sync_with_i3; +} + +my $ws = fresh_workspace; + +my @content = @{get_ws_content($ws)}; +is(@content, 0, 'no nodes on the new workspace yet'); + +my ($fh, $filename) = tempfile(UNLINK => 1); +print $fh <flush; +cmd "append_layout $filename"; + +@content = @{get_ws_content($ws)}; +is(@content, 1, 'one node on the workspace now'); + +my $window = open_window( + name => 'original_title', + wm_class => 'a', +); + +@content = @{get_ws_content($ws)}; +is(@content, 2, 'two nodes on the workspace now'); + +change_window_title($window, "different_title"); + +does_i3_live; + +@content = @{get_ws_content($ws)}; +my @nodes = @{$content[0]->{nodes}}; +is(@content, 1, 'only one node on the workspace now'); +is($nodes[0]->{name}, 'different_title', 'test window got swallowed'); + +close($fh); + +done_testing; From fd7e51927d2c79d0288db4195c2000451d4e300b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 13 Aug 2019 16:44:45 +0300 Subject: [PATCH 07/76] get_first_output: prefer primary output Used in two cases: - When the pointer location can't be found but some initial container needs to be focused - When moving disabled outputs --- src/randr.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/randr.c b/src/randr.c index fb127ab5..9a0bf5cc 100644 --- a/src/randr.c +++ b/src/randr.c @@ -70,11 +70,22 @@ Output *get_output_by_name(const char *name, const bool require_active) { * */ Output *get_first_output(void) { - Output *output; + Output *output, *result = NULL; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->active) - return output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (output->active) { + if (output->primary) { + return output; + } + if (!result) { + result = output; + } + } + } + + if (result) { + return result; + } die("No usable outputs available.\n"); } From 14d2a4c7f658a997d9bba5c714631f922dd68f12 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 13 Aug 2019 16:54:55 +0300 Subject: [PATCH 08/76] Correctly select output when pointer query fails --- src/main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index 7ba12bd9..5c86a21e 100644 --- a/src/main.c +++ b/src/main.c @@ -813,12 +813,13 @@ int main(int argc, char *argv[]) { if (!output) { ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n", pointerreply->root_x, pointerreply->root_y); - output = get_first_output(); } - - con_activate(con_descend_focused(output_get_content(output->con))); - free(pointerreply); } + if (!output) { + output = get_first_output(); + } + con_activate(con_descend_focused(output_get_content(output->con))); + free(pointerreply); tree_render(); From 98b50d7dd0b1b5a2fa1d3a1c2a1f4841ce6e8296 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 15 Aug 2019 20:33:33 +0200 Subject: [PATCH 09/76] default config: immediately refresh i3status after volume changes --- etc/config | 7 ++++--- etc/config.keycodes | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/etc/config b/etc/config index a0f3b84d..3c69e8fc 100644 --- a/etc/config +++ b/etc/config @@ -29,9 +29,10 @@ exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork exec --no-startup-id nm-applet # Use pactl to adjust volume in PulseAudio. -bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% -bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% -bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle +set $refresh_i3status killall -SIGUSR1 i3status +bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% && $refresh_i3status +bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% && $refresh_i3status +bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status # use these keys for focus, movement, and resize directions when reaching for # the arrows is not convenient diff --git a/etc/config.keycodes b/etc/config.keycodes index 6045ea68..82808f1e 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -30,9 +30,10 @@ exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork exec --no-startup-id nm-applet # Use pactl to adjust volume in PulseAudio. -bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% -bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% -bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle +set $refresh_i3status killall -SIGUSR1 i3status +bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% && $refresh_i3status +bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% && $refresh_i3status +bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status # Use Mouse+$mod to drag floating windows to their wanted position floating_modifier $mod From 31fd13aa1bc30c0a7c63264f6cfb4678162f5e9f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 15 Aug 2019 20:56:58 +0200 Subject: [PATCH 10/76] default config: add XF86AudioMicMute --- etc/config | 1 + etc/config.keycodes | 1 + 2 files changed, 2 insertions(+) diff --git a/etc/config b/etc/config index 3c69e8fc..a1cb8422 100644 --- a/etc/config +++ b/etc/config @@ -33,6 +33,7 @@ set $refresh_i3status killall -SIGUSR1 i3status bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% && $refresh_i3status bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% && $refresh_i3status bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status +bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle && $refresh_i3status # use these keys for focus, movement, and resize directions when reaching for # the arrows is not convenient diff --git a/etc/config.keycodes b/etc/config.keycodes index 82808f1e..83f37af2 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -34,6 +34,7 @@ set $refresh_i3status killall -SIGUSR1 i3status bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% && $refresh_i3status bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% && $refresh_i3status bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status +bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle && $refresh_i3status # Use Mouse+$mod to drag floating windows to their wanted position floating_modifier $mod From 61ae2f17df270ba1d5fffe1641c3f9db1b9c0cc0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 15 Aug 2019 21:16:12 +0200 Subject: [PATCH 11/76] default config: mention loginctl lock-session alongside xss-lock --- etc/config | 2 +- etc/config.keycodes | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/config b/etc/config index a1cb8422..597ec4eb 100644 --- a/etc/config +++ b/etc/config @@ -21,7 +21,7 @@ font pango:monospace 8 # they are included here as an example. Modify as you see fit. # xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the -# screen before suspend. +# screen before suspend. Use loginctl lock-session to lock your screen. exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork # NetworkManager is the most popular way to manage wireless networks on Linux, diff --git a/etc/config.keycodes b/etc/config.keycodes index 83f37af2..7988a01b 100644 --- a/etc/config.keycodes +++ b/etc/config.keycodes @@ -22,7 +22,7 @@ font pango:monospace 8 # they are included here as an example. Modify as you see fit. # xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the -# screen before suspend. +# screen before suspend. Use loginctl lock-session to lock your screen. exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork # NetworkManager is the most popular way to manage wireless networks on Linux, From 741e94ae4fe662b8927f9abb3ad91b576006c718 Mon Sep 17 00:00:00 2001 From: Brian Ashworth Date: Wed, 21 Aug 2019 19:58:49 -0400 Subject: [PATCH 12/76] cmd_move_to_mark: fix move to scratchpad hidden This fixes the case where moving a container to a scratchpad hidden container via a mark would cause the container to be tiling on the __i3_scratch workspace. This still moves the container to the __i3_scratch workspace, but properly adds it to the scratchpad so that it becomes usable instead of requiring criteria to regain access to. --- src/con.c | 7 +++++++ testcases/t/243-move-to-mark.t | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/con.c b/src/con.c index 52c78d0c..0f913e0a 100644 --- a/src/con.c +++ b/src/con.c @@ -1298,6 +1298,13 @@ bool con_move_to_mark(Con *con, const char *mark) { return false; } + /* For target containers in the scratchpad, we just send the window to the scratchpad. */ + if (con_get_workspace(target) == workspace_get("__i3_scratch", NULL)) { + DLOG("target container is in the scratchpad, moving container to scratchpad.\n"); + scratchpad_move(con); + return true; + } + /* For floating target containers, we just send the window to the same workspace. */ if (con_is_floating(target)) { DLOG("target container is floating, moving container to target's workspace.\n"); diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t index 5e806cd4..b6ec462e 100644 --- a/testcases/t/243-move-to-mark.t +++ b/testcases/t/243-move-to-mark.t @@ -24,6 +24,7 @@ use i3test; my ($A, $B, $S, $M, $F, $source_ws, $target_ws, $ws); my ($nodes, $focus); +my $__i3_scratch; my $cmd_result; my $_NET_WM_STATE_REMOVE = 0; @@ -401,6 +402,29 @@ is(@{$nodes}, 2, 'there is a window and a container with the contents of the ori is($nodes->[0]->{window}, $M->{id}, 'M remains the first window'); is(@{get_ws($target_ws)->{floating_nodes}}, 1, 'target workspace has the floating container'); +############################################################################### +# Given 'S' and 'M', where 'S' is a container and 'M' is a container hidden in +# the scratchpad, then move 'S' to the scratchpad +############################################################################### + +$ws = fresh_workspace; +$S = open_window; +cmd 'mark S'; +$M = open_window; +cmd 'mark target'; +cmd 'move container to scratchpad'; + +cmd '[con_mark=S] move container to mark target'; +sync_with_i3; + +($nodes, $focus) = get_ws_content($ws); +is(@{$nodes}, 0, 'there are no tiling windows on the workspace'); +is(@{get_ws($ws)->{floating_nodes}}, 0, 'there are no floating containers on the workspace'); + +$__i3_scratch = get_ws('__i3_scratch'); +is(@{$__i3_scratch->{nodes}}, 0, 'there are no tiling windows on the scratchpad workspace'); +is(@{$__i3_scratch->{floating_nodes}}, 2, 'there are two floating containers in the scratchpad'); + ############################################################################### done_testing; From efe84764b0f34fa83bca018031980b50d528a3c8 Mon Sep 17 00:00:00 2001 From: David Shen <2984600+pantherman594@users.noreply.github.com> Date: Wed, 28 Aug 2019 02:54:10 -0400 Subject: [PATCH 13/76] Unset _I3_RESTART_FD after restart (#3775) Closes #3764 Closes #3765 --- src/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.c b/src/main.c index 5c86a21e..d7d9216a 100644 --- a/src/main.c +++ b/src/main.c @@ -868,6 +868,7 @@ int main(int argc, char *argv[]) { DLOG("serving restart fd %d", restart_fd); ipc_client *client = ipc_new_client_on_fd(main_loop, restart_fd); ipc_confirm_restart(client); + unsetenv("_I3_RESTART_FD"); } } From c0f987fd6677246295dc6d80a68115628f7bfbcb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Aug 2019 23:06:59 +0200 Subject: [PATCH 14/76] Update debian/changelog --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 65e5402a..32366e81 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,7 +2,7 @@ i3-wm (4.17.1-1) unstable; urgency=medium * New upstream release. - -- Michael Stapelberg Sat, 03 Aug 2019 15:14:28 +0200 + -- Michael Stapelberg Fri, 30 Aug 2019 23:06:40 +0200 i3-wm (4.17-1) unstable; urgency=medium From 39a65166c4e5d05225579c3c39944b667096f615 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Aug 2019 23:46:42 +0200 Subject: [PATCH 15/76] update debian/changelog --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 32366e81..c5ea756d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (4.17.2-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Fri, 30 Aug 2019 23:06:40 +0200 + i3-wm (4.17.1-1) unstable; urgency=medium * New upstream release. From 1e4ffcafaa7b456f0096adf2ca3babc005b9b87a Mon Sep 17 00:00:00 2001 From: "Erwin J. van Eijk" <235739+erwinvaneijk@users.noreply.github.com> Date: Sun, 1 Sep 2019 19:15:33 +0200 Subject: [PATCH 16/76] fix lcov support Ubuntu 18.10 comes with lcov 1.13, which is now added. --- m4/ax_code_coverage.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m4/ax_code_coverage.m4 b/m4/ax_code_coverage.m4 index 6c985ebc..1f1bc702 100644 --- a/m4/ax_code_coverage.m4 +++ b/m4/ax_code_coverage.m4 @@ -107,7 +107,7 @@ AC_DEFUN([AX_CODE_COVERAGE],[ ]) # List of supported lcov versions. - lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12" + lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13" AC_CHECK_PROG([LCOV], [lcov], [lcov]) AC_CHECK_PROG([GENHTML], [genhtml], [genhtml]) From 148bdeefdcd57738020d6a4c72137235584a52e8 Mon Sep 17 00:00:00 2001 From: Iskustvo Date: Sun, 1 Sep 2019 19:46:45 +0200 Subject: [PATCH 17/76] Added documentation for "fullscreen_mode" in GET_TREE reply. --- docs/ipc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/ipc b/docs/ipc index ea7a5892..df93a7d1 100644 --- a/docs/ipc +++ b/docs/ipc @@ -357,6 +357,14 @@ focus (array of integer):: order. Traversing the tree by following the first entry in this array will result in eventually reaching the one node with +focused+ set to true. +fullscreen_mode (integer):: + Whether this container is in fullscreen state or not. + Possible values are + +0+ (no fullscreen), + +1+ (fullscreened on output) or + +2+ (fullscreened globally). + Note that all workspaces are considered fullscreened on their respective output. + nodes (array of node):: The tiling (i.e. non-floating) child containers of this node. floating_nodes (array of node):: From a73510026f17713e6929f32723b8d23e88bf6998 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 3 Sep 2019 10:43:36 +0300 Subject: [PATCH 18/76] Remove packed attribute from Rect Fixes #3785 -- the issue where the Travis build failed because of gcc's -Werror=address-of-packed-member. Adds an equality function to avoid relying on memcmp(). --- include/data.h | 2 +- include/util.h | 1 + src/commands.c | 2 +- src/floating.c | 3 +-- src/load_layout.c | 3 +-- src/scratchpad.c | 2 +- src/util.c | 4 ++++ src/x.c | 7 +++---- 8 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/data.h b/include/data.h index 4c6ecb8c..82027f75 100644 --- a/include/data.h +++ b/include/data.h @@ -159,7 +159,7 @@ struct Rect { uint32_t y; uint32_t width; uint32_t height; -} __attribute__((packed)); +}; /** * Stores the reserved pixels on each screen edge read from a diff --git a/include/util.h b/include/util.h index d08ac69d..7a2b3083 100644 --- a/include/util.h +++ b/include/util.h @@ -64,6 +64,7 @@ int max(int a, int b); bool rect_contains(Rect rect, uint32_t x, uint32_t y); Rect rect_add(Rect a, Rect b); Rect rect_sub(Rect a, Rect b); +bool rect_equals(Rect a, Rect b); /** * Returns true if the name consists of only digits. diff --git a/src/commands.c b/src/commands.c index aadf204f..5b11fce1 100644 --- a/src/commands.c +++ b/src/commands.c @@ -469,7 +469,7 @@ static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_s /* Did we actually resize anything or did the size constraints prevent us? * If we could not resize, exit now to not move the window. */ - if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) { + if (rect_equals(old_rect, floating_con->rect)) { return; } diff --git a/src/floating.c b/src/floating.c index 79f1d3d3..9a721301 100644 --- a/src/floating.c +++ b/src/floating.c @@ -322,11 +322,10 @@ void floating_enable(Con *con, bool automatic) { DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height); DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height); - Rect zero = {0, 0, 0, 0}; nc->rect = con->geometry; /* If the geometry was not set (split containers), we need to determine a * sensible one by combining the geometry of all children */ - if (memcmp(&(nc->rect), &zero, sizeof(Rect)) == 0) { + if (rect_equals(nc->rect, (Rect){0, 0, 0, 0})) { DLOG("Geometry not set, combining children\n"); Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { diff --git a/src/load_layout.c b/src/load_layout.c index 47daada1..4f107cd6 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -141,8 +141,7 @@ static int json_end_map(void *ctx) { // Also set a size if none was supplied, otherwise the placeholder // window cannot be created as X11 requests with width=0 or // height=0 are invalid. - const Rect zero = {0, 0, 0, 0}; - if (memcmp(&(json_node->rect), &zero, sizeof(Rect)) == 0) { + if (rect_equals(json_node->rect, (Rect){0, 0, 0, 0})) { DLOG("Geometry not set, combining children\n"); Con *child; TAILQ_FOREACH(child, &(json_node->nodes_head), nodes) { diff --git a/src/scratchpad.c b/src/scratchpad.c index b7fbcc92..b74e4a6f 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -279,7 +279,7 @@ void scratchpad_fix_resolution(void) { Rect new_rect = __i3_output->rect; - if (memcmp(&old_rect, &new_rect, sizeof(Rect)) == 0) { + if (rect_equals(new_rect, old_rect)) { DLOG("Scratchpad size unchanged.\n"); return; } diff --git a/src/util.c b/src/util.c index 9fe3fa44..812aad37 100644 --- a/src/util.c +++ b/src/util.c @@ -53,6 +53,10 @@ Rect rect_sub(Rect a, Rect b) { a.height - b.height}; } +bool rect_equals(Rect a, Rect b) { + return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; +} + /* * Returns true if the name consists of only digits. * diff --git a/src/x.c b/src/x.c index e6d875e5..43824bbc 100644 --- a/src/x.c +++ b/src/x.c @@ -251,8 +251,7 @@ void x_move_win(Con *src, Con *dest) { state_dest->con = state_src->con; state_src->con = NULL; - Rect zero = {0, 0, 0, 0}; - if (memcmp(&(state_dest->window_rect), &(zero), sizeof(Rect)) == 0) { + if (rect_equals(state_dest->window_rect, (Rect){0, 0, 0, 0})) { memcpy(&(state_dest->window_rect), &(state_src->window_rect), sizeof(Rect)); DLOG("COPYING RECT\n"); } @@ -929,7 +928,7 @@ void x_push_node(Con *con) { bool fake_notify = false; /* Set new position if rect changed (and if height > 0) or if the pixmap * needs to be recreated */ - if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (memcmp(&(state->rect), &rect, sizeof(Rect)) != 0 && + if ((is_pixmap_needed && con->frame_buffer.id == XCB_NONE) || (!rect_equals(state->rect, rect) && rect.height > 0)) { /* We first create the new pixmap, then render to it, set it as the * background and only afterwards change the window size. This reduces @@ -1008,7 +1007,7 @@ void x_push_node(Con *con) { /* dito, but for child windows */ if (con->window != NULL && - memcmp(&(state->window_rect), &(con->window_rect), sizeof(Rect)) != 0) { + !rect_equals(state->window_rect, con->window_rect)) { DLOG("setting window rect (%d, %d, %d, %d)\n", con->window_rect.x, con->window_rect.y, con->window_rect.width, con->window_rect.height); xcb_set_window_rect(conn, con->window->id, con->window_rect); From ff73ddeeee71571e2f40fa3be430ef83cd13b15e Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Mon, 23 Sep 2019 08:13:05 +0000 Subject: [PATCH 19/76] extract_workspace_names_from_bindings: handle optional flags fixes #3527 --- src/workspace.c | 15 ++++++++++++--- testcases/t/172-start-on-named-ws.t | 18 ++++++++++++++++++ testcases/t/270-config-no-newline-end.t | 3 +-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 4f618751..4a1c4de8 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -197,18 +197,27 @@ void extract_workspace_names_from_bindings(void) { while (*target == ' ' || *target == '\t') target++; /* We check if this is the workspace - * next/prev/next_on_output/prev_on_output/back_and_forth/number command. + * next/prev/next_on_output/prev_on_output/back_and_forth command. * Beware: The workspace names "next", "prev", "next_on_output", - * "prev_on_output", "number", "back_and_forth" and "current" are OK, + * "prev_on_output", "back_and_forth" and "current" are OK, * so we check before stripping the double quotes */ if (strncasecmp(target, "next", strlen("next")) == 0 || strncasecmp(target, "prev", strlen("prev")) == 0 || strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 || strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 || - strncasecmp(target, "number", strlen("number")) == 0 || strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 || strncasecmp(target, "current", strlen("current")) == 0) continue; + if (strncasecmp(target, "--no-auto-back-and-forth", strlen("--no-auto-back-and-forth")) == 0) { + target += strlen("--no-auto-back-and-forth"); + while (*target == ' ' || *target == '\t') + target++; + } + if (strncasecmp(target, "number", strlen("number")) == 0) { + target += strlen("number"); + while (*target == ' ' || *target == '\t') + target++; + } char *target_name = parse_string(&target, false); if (target_name == NULL) continue; diff --git a/testcases/t/172-start-on-named-ws.t b/testcases/t/172-start-on-named-ws.t index 778eb23b..d1af6c07 100644 --- a/testcases/t/172-start-on-named-ws.t +++ b/testcases/t/172-start-on-named-ws.t @@ -125,4 +125,22 @@ is_deeply(\@names, [ '3' ], 'i3 starts on workspace 3'); exit_gracefully($pid); +############################################################## +# 7: verify optional flags do not affect startup workspace +############################################################## + +$config = < 0; my $first_lines = <<'EOT'; -set $workspace1 workspace number 1 set $workspace0 workspace eggs -bindsym Mod4+1 $workspace1 +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 EOT # Intentionally don't add a trailing newline for the last line since this is From 46cf9fb91b09f0b9508d50ebae6fd0cad9e32aa9 Mon Sep 17 00:00:00 2001 From: acheronfail Date: Fri, 20 Sep 2019 15:58:15 +1000 Subject: [PATCH 20/76] feat: add window_type to ipc response --- src/ipc.c | 30 ++++++++++++++++++++++++++++++ testcases/t/116-nestedcons.t | 1 + 2 files changed, 31 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 0ffdfebf..6a49d695 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -528,6 +528,36 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { else y(null); + ystr("window_type"); + if (con->window) { + if (con->window->window_type == A__NET_WM_WINDOW_TYPE_NORMAL) { + ystr("normal"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DOCK) { + ystr("dock"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DIALOG) { + ystr("dialog"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_UTILITY) { + ystr("utility"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_TOOLBAR) { + ystr("toolbar"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_SPLASH) { + ystr("splash"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_MENU) { + ystr("menu"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU) { + ystr("dropdown_menu"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_POPUP_MENU) { + ystr("popup_menu"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_TOOLTIP) { + ystr("tooltip"); + } else if (con->window->window_type == A__NET_WM_WINDOW_TYPE_NOTIFICATION) { + ystr("notification"); + } else { + ystr("unknown"); + } + } else + y(null); + if (con->window && !inplace_restart) { /* Window properties are useless to preserve when restarting because * they will be queried again anyway. However, for i3-save-tree(1), diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index 40ad6bb3..4f13b1e5 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -53,6 +53,7 @@ my $expected = { name => 'root', orientation => $ignore, type => 'root', + window_type => undef, id => $ignore, rect => $ignore, deco_rect => $ignore, From 67217822b8f11fb88df4e8752f08274ef741e122 Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Tue, 1 Oct 2019 07:24:21 +0000 Subject: [PATCH 21/76] do not try to center floating window on itself Some apps including XTerm start with a WM_CLIENT_LEADER property containing their own window ID. Before this commit, i3 tried to center such windows onto itself and did it wrong since `leader->rect == {0,0,0,0}` at this moment. The first affected commit is 128122e7663a5a1f38bd8f921ecaef55ff2a4b13, however, before it such windows already was misplaced, but got sanitized afterward [1]. [1]: https://github.com/i3/i3/blob/8a3ef3a81bd4946777c7e3585384283bf12d89be/src/floating.c#L329-L335 Fixes #3606 --- src/floating.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/floating.c b/src/floating.c index 9a721301..70e7bc17 100644 --- a/src/floating.c +++ b/src/floating.c @@ -371,6 +371,7 @@ void floating_enable(Con *con, bool automatic) { if (nc->rect.x == 0 && nc->rect.y == 0) { Con *leader; if (con->window && con->window->leader != XCB_NONE && + con->window->id != con->window->leader && (leader = con_by_window_id(con->window->leader)) != NULL) { DLOG("Centering above leader\n"); floating_center(nc, leader->rect); From 454473ac6c91a0f72e9e940841e44a29b9bb697c Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Wed, 12 Dec 2018 11:01:36 +0700 Subject: [PATCH 22/76] Move drag_pointer() to its own source file Move drag_pointer() and related definitions from floating.c to new file drag_pointer.c since it's applicable not only to floating windows but also to resizing of tiled windows. --- Makefile.am | 2 + include/all.h | 1 + include/drag.h | 55 ++++++++++++ include/floating.h | 43 --------- src/drag.c | 213 +++++++++++++++++++++++++++++++++++++++++++++ src/floating.c | 203 ------------------------------------------ 6 files changed, 271 insertions(+), 246 deletions(-) create mode 100644 include/drag.h create mode 100644 src/drag.c diff --git a/Makefile.am b/Makefile.am index ee0e038b..d379a854 100644 --- a/Makefile.am +++ b/Makefile.am @@ -503,6 +503,7 @@ i3_SOURCES = \ include/con.h \ include/data.h \ include/display_version.h \ + include/drag.h \ include/ewmh.h \ include/fake_outputs.h \ include/floating.h \ @@ -548,6 +549,7 @@ i3_SOURCES = \ src/config_directives.c \ src/config_parser.c \ src/display_version.c \ + src/drag.c \ src/ewmh.c \ src/fake_outputs.c \ src/floating.c \ diff --git a/include/all.h b/include/all.h index e93b066b..aa2b5b25 100644 --- a/include/all.h +++ b/include/all.h @@ -53,6 +53,7 @@ #include "click.h" #include "key_press.h" #include "floating.h" +#include "drag.h" #include "configuration.h" #include "handlers.h" #include "randr.h" diff --git a/include/drag.h b/include/drag.h new file mode 100644 index 00000000..5d4ca581 --- /dev/null +++ b/include/drag.h @@ -0,0 +1,55 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * drag.c: click and drag. + * + */ +#pragma once + +#include + +/** Callback for dragging */ +typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t, const void *); + +/** Macro to create a callback function for dragging */ +#define DRAGGING_CB(name) \ + static void name(Con *con, Rect *old_rect, uint32_t new_x, \ + uint32_t new_y, const void *extra) + +/** + * This is the return value of a drag operation like drag_pointer. + * + * DRAGGING will indicate the drag action is still in progress and can be + * continued or resolved. + * + * DRAG_SUCCESS will indicate the intention of the drag action should be + * carried out. + * + * DRAG_REVERT will indicate an attempt should be made to restore the state of + * the involved windows to their condition before the drag. + * + * DRAG_ABORT will indicate that the intention of the drag action cannot be + * carried out (e.g. because the window has been unmapped). + * + */ +typedef enum { + DRAGGING = 0, + DRAG_SUCCESS, + DRAG_REVERT, + DRAG_ABORT +} drag_result_t; + +/** + * This function grabs your pointer and keyboard and lets you drag stuff around + * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will + * be received and the given callback will be called with the parameters + * specified (client, border on which the click originally was), the original + * rect of the client, the event and the new coordinates (x, y). + * + */ +drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, + xcb_window_t confine_to, border_t border, int cursor, + callback_t callback, const void *extra); diff --git a/include/floating.h b/include/floating.h index a7813099..4b66eef7 100644 --- a/include/floating.h +++ b/include/floating.h @@ -13,14 +13,6 @@ #include "tree.h" -/** Callback for dragging */ -typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t, const void *); - -/** Macro to create a callback function for dragging */ -#define DRAGGING_CB(name) \ - static void name(Con *con, Rect *old_rect, uint32_t new_x, \ - uint32_t new_y, const void *extra) - /** On which border was the dragging initiated? */ typedef enum { BORDER_LEFT = (1 << 0), BORDER_RIGHT = (1 << 1), @@ -106,41 +98,6 @@ void floating_resize_window(Con *con, const bool proportional, const xcb_button_ */ void floating_check_size(Con *floating_con, bool prefer_height); -/** - * This is the return value of a drag operation like drag_pointer. - * - * DRAGGING will indicate the drag action is still in progress and can be - * continued or resolved. - * - * DRAG_SUCCESS will indicate the intention of the drag action should be - * carried out. - * - * DRAG_REVERT will indicate an attempt should be made to restore the state of - * the involved windows to their condition before the drag. - * - * DRAG_ABORT will indicate that the intention of the drag action cannot be - * carried out (e.g. because the window has been unmapped). - * - */ -typedef enum { - DRAGGING = 0, - DRAG_SUCCESS, - DRAG_REVERT, - DRAG_ABORT -} drag_result_t; - -/** - * This function grabs your pointer and keyboard and lets you drag stuff around - * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will - * be received and the given callback will be called with the parameters - * specified (client, border on which the click originally was), the original - * rect of the client, the event and the new coordinates (x, y). - * - */ -drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, int cursor, - callback_t callback, const void *extra); - /** * Repositions the CT_FLOATING_CON to have the coordinates specified by * newrect, but only if the coordinates are not out-of-bounds. Also reassigns diff --git a/src/drag.c b/src/drag.c new file mode 100644 index 00000000..af019b25 --- /dev/null +++ b/src/drag.c @@ -0,0 +1,213 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * drag.c: click and drag. + * + */ +#include "all.h" + +/* Custom data structure used to track dragging-related events. */ +struct drag_x11_cb { + ev_prepare prepare; + + /* Whether this modal event loop should be exited and with which result. */ + drag_result_t result; + + /* The container that is being dragged or resized, or NULL if this is a + * drag of the resize handle. */ + Con *con; + + /* The dimensions of con when the loop was started. */ + Rect old_rect; + + /* The callback to invoke after every pointer movement. */ + callback_t callback; + + /* User data pointer for callback. */ + const void *extra; +}; + +static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) { + xcb_motion_notify_event_t *last_motion_notify = NULL; + xcb_generic_event_t *event; + + while ((event = xcb_poll_for_event(conn)) != NULL) { + if (event->response_type == 0) { + xcb_generic_error_t *error = (xcb_generic_error_t *)event; + DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n", + error->sequence, error->error_code); + free(event); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + switch (type) { + case XCB_BUTTON_RELEASE: + dragloop->result = DRAG_SUCCESS; + break; + + case XCB_KEY_PRESS: + DLOG("A key was pressed during drag, reverting changes.\n"); + dragloop->result = DRAG_REVERT; + handle_event(type, event); + break; + + case XCB_UNMAP_NOTIFY: { + xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event; + Con *con = con_by_window_id(unmap_event->window); + + if (con != NULL) { + DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con); + + if (con_get_workspace(con) == con_get_workspace(focused)) { + DLOG("UnmapNotify for a managed window on the current workspace, aborting\n"); + dragloop->result = DRAG_ABORT; + } + } + + handle_event(type, event); + break; + } + + case XCB_MOTION_NOTIFY: + /* motion_notify events are saved for later */ + FREE(last_motion_notify); + last_motion_notify = (xcb_motion_notify_event_t *)event; + break; + + default: + DLOG("Passing to original handler\n"); + handle_event(type, event); + break; + } + + if (last_motion_notify != (xcb_motion_notify_event_t *)event) + free(event); + + if (dragloop->result != DRAGGING) { + ev_break(EV_A_ EVBREAK_ONE); + if (dragloop->result == DRAG_SUCCESS) { + /* Ensure motion notify events are handled. */ + break; + } else { + free(last_motion_notify); + return true; + } + } + } + + if (last_motion_notify == NULL) { + return true; + } + + /* 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); + + xcb_flush(conn); + return dragloop->result != DRAGGING; +} + +static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { + struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data; + while (!drain_drag_events(EV_A, dragloop)) { + /* repeatedly drain events: draining might produce additional ones */ + } +} + +/* + * This function grabs your pointer and keyboard and lets you drag stuff around + * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will + * be received and the given callback will be called with the parameters + * specified (client, border on which the click originally was), the original + * rect of the client, the event and the new coordinates (x, y). + * + */ +drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, + border_t border, int cursor, callback_t callback, const void *extra) { + xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE; + + /* Grab the pointer */ + xcb_grab_pointer_cookie_t cookie; + xcb_grab_pointer_reply_t *reply; + xcb_generic_error_t *error; + + cookie = xcb_grab_pointer(conn, + false, /* get all pointer events specified by the following mask */ + root, /* grab the root window */ + XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ + XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ + XCB_GRAB_MODE_ASYNC, /* keyboard mode */ + confine_to, /* confine_to = in which window should the cursor stay */ + xcursor, /* possibly display a special cursor */ + XCB_CURRENT_TIME); + + if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) { + ELOG("Could not grab pointer (error_code = %d)\n", error->error_code); + free(error); + return DRAG_ABORT; + } + + free(reply); + + /* Grab the keyboard */ + xcb_grab_keyboard_cookie_t keyb_cookie; + xcb_grab_keyboard_reply_t *keyb_reply; + + keyb_cookie = xcb_grab_keyboard(conn, + false, /* get all keyboard events */ + root, /* grab the root window */ + XCB_CURRENT_TIME, + XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */ + XCB_GRAB_MODE_ASYNC /* keyboard mode */ + ); + + if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) { + ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code); + free(error); + xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + return DRAG_ABORT; + } + + free(keyb_reply); + + /* Go into our own event loop */ + struct drag_x11_cb loop = { + .result = DRAGGING, + .con = con, + .callback = callback, + .extra = extra, + }; + ev_prepare *prepare = &loop.prepare; + if (con) + loop.old_rect = con->rect; + ev_prepare_init(prepare, xcb_drag_prepare_cb); + prepare->data = &loop; + main_set_x11_cb(false); + ev_prepare_start(main_loop, prepare); + + ev_loop(main_loop, 0); + + ev_prepare_stop(main_loop, prepare); + main_set_x11_cb(true); + + xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); + xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + xcb_flush(conn); + + return loop.result; +} diff --git a/src/floating.c b/src/floating.c index 70e7bc17..b022ee19 100644 --- a/src/floating.c +++ b/src/floating.c @@ -727,209 +727,6 @@ void floating_resize_window(Con *con, const bool proportional, con->scratchpad_state = SCRATCHPAD_CHANGED; } -/* Custom data structure used to track dragging-related events. */ -struct drag_x11_cb { - ev_prepare prepare; - - /* Whether this modal event loop should be exited and with which result. */ - drag_result_t result; - - /* The container that is being dragged or resized, or NULL if this is a - * drag of the resize handle. */ - Con *con; - - /* The dimensions of con when the loop was started. */ - Rect old_rect; - - /* The callback to invoke after every pointer movement. */ - callback_t callback; - - /* User data pointer for callback. */ - const void *extra; -}; - -static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) { - xcb_motion_notify_event_t *last_motion_notify = NULL; - xcb_generic_event_t *event; - - while ((event = xcb_poll_for_event(conn)) != NULL) { - if (event->response_type == 0) { - xcb_generic_error_t *error = (xcb_generic_error_t *)event; - DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n", - error->sequence, error->error_code); - free(event); - continue; - } - - /* Strip off the highest bit (set if the event is generated) */ - int type = (event->response_type & 0x7F); - - switch (type) { - case XCB_BUTTON_RELEASE: - dragloop->result = DRAG_SUCCESS; - break; - - case XCB_KEY_PRESS: - DLOG("A key was pressed during drag, reverting changes.\n"); - dragloop->result = DRAG_REVERT; - handle_event(type, event); - break; - - case XCB_UNMAP_NOTIFY: { - xcb_unmap_notify_event_t *unmap_event = (xcb_unmap_notify_event_t *)event; - Con *con = con_by_window_id(unmap_event->window); - - if (con != NULL) { - DLOG("UnmapNotify for window 0x%08x (container %p)\n", unmap_event->window, con); - - if (con_get_workspace(con) == con_get_workspace(focused)) { - DLOG("UnmapNotify for a managed window on the current workspace, aborting\n"); - dragloop->result = DRAG_ABORT; - } - } - - handle_event(type, event); - break; - } - - case XCB_MOTION_NOTIFY: - /* motion_notify events are saved for later */ - FREE(last_motion_notify); - last_motion_notify = (xcb_motion_notify_event_t *)event; - break; - - default: - DLOG("Passing to original handler\n"); - handle_event(type, event); - break; - } - - if (last_motion_notify != (xcb_motion_notify_event_t *)event) - free(event); - - if (dragloop->result != DRAGGING) { - ev_break(EV_A_ EVBREAK_ONE); - if (dragloop->result == DRAG_SUCCESS) { - /* Ensure motion notify events are handled. */ - break; - } else { - free(last_motion_notify); - return true; - } - } - } - - if (last_motion_notify == NULL) { - return true; - } - - /* 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); - - xcb_flush(conn); - return dragloop->result != DRAGGING; -} - -static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { - struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data; - while (!drain_drag_events(EV_A, dragloop)) { - /* repeatedly drain events: draining might produce additional ones */ - } -} - -/* - * This function grabs your pointer and keyboard and lets you drag stuff around - * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will - * be received and the given callback will be called with the parameters - * specified (client, border on which the click originally was), the original - * rect of the client, the event and the new coordinates (x, y). - * - */ -drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, - border_t border, int cursor, callback_t callback, const void *extra) { - xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE; - - /* Grab the pointer */ - xcb_grab_pointer_cookie_t cookie; - xcb_grab_pointer_reply_t *reply; - xcb_generic_error_t *error; - - cookie = xcb_grab_pointer(conn, - false, /* get all pointer events specified by the following mask */ - root, /* grab the root window */ - XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ - XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ - XCB_GRAB_MODE_ASYNC, /* keyboard mode */ - confine_to, /* confine_to = in which window should the cursor stay */ - xcursor, /* possibly display a special cursor */ - XCB_CURRENT_TIME); - - if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) { - ELOG("Could not grab pointer (error_code = %d)\n", error->error_code); - free(error); - return DRAG_ABORT; - } - - free(reply); - - /* Grab the keyboard */ - xcb_grab_keyboard_cookie_t keyb_cookie; - xcb_grab_keyboard_reply_t *keyb_reply; - - keyb_cookie = xcb_grab_keyboard(conn, - false, /* get all keyboard events */ - root, /* grab the root window */ - XCB_CURRENT_TIME, - XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */ - XCB_GRAB_MODE_ASYNC /* keyboard mode */ - ); - - if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) { - ELOG("Could not grab keyboard (error_code = %d)\n", error->error_code); - free(error); - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); - return DRAG_ABORT; - } - - free(keyb_reply); - - /* Go into our own event loop */ - struct drag_x11_cb loop = { - .result = DRAGGING, - .con = con, - .callback = callback, - .extra = extra, - }; - ev_prepare *prepare = &loop.prepare; - if (con) - loop.old_rect = con->rect; - ev_prepare_init(prepare, xcb_drag_prepare_cb); - prepare->data = &loop; - main_set_x11_cb(false); - ev_prepare_start(main_loop, prepare); - - ev_loop(main_loop, 0); - - ev_prepare_stop(main_loop, prepare); - main_set_x11_cb(true); - - xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); - xcb_flush(conn); - - return loop.result; -} - /* * Repositions the CT_FLOATING_CON to have the coordinates specified by * newrect, but only if the coordinates are not out-of-bounds. Also reassigns From 2795c51d4b4f1c414aaa0c493061c908ac0260f1 Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Wed, 12 Dec 2018 11:12:05 +0700 Subject: [PATCH 23/76] drag_pointer(): drop unused parameter `border` --- include/drag.h | 2 +- src/drag.c | 2 +- src/floating.c | 4 ++-- src/resize.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/drag.h b/include/drag.h index 5d4ca581..924ff98c 100644 --- a/include/drag.h +++ b/include/drag.h @@ -51,5 +51,5 @@ typedef enum { * */ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, int cursor, + xcb_window_t confine_to, int cursor, callback_t callback, const void *extra); diff --git a/src/drag.c b/src/drag.c index af019b25..57d9f9a9 100644 --- a/src/drag.c +++ b/src/drag.c @@ -138,7 +138,7 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { * */ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, - border_t border, int cursor, callback_t callback, const void *extra) { + int cursor, callback_t callback, const void *extra) { xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE; /* Grab the pointer */ diff --git a/src/floating.c b/src/floating.c index b022ee19..6772128a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -595,7 +595,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { Rect initial_rect = con->rect; /* Drag the window */ - drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event); + drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, drag_window_callback, event); if (!con_exists(con)) { DLOG("The container has been closed in the meantime.\n"); @@ -711,7 +711,7 @@ void floating_resize_window(Con *con, const bool proportional, /* get the initial rect in case of revert/cancel */ Rect initial_rect = con->rect; - drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms); + drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, resize_window_callback, ¶ms); if (!con_exists(con)) { DLOG("The container has been closed in the meantime.\n"); diff --git a/src/resize.c b/src/resize.c index 5ddee5c1..a63e337b 100644 --- a/src/resize.c +++ b/src/resize.c @@ -208,7 +208,7 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation const struct callback_params params = {orientation, output, helpwin, &new_position}; /* `drag_pointer' blocks until the drag is completed. */ - drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms); + drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); From 551ec20941060a02b1d621abfd4c43731176c5d8 Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Wed, 12 Dec 2018 15:35:11 +0700 Subject: [PATCH 24/76] drag_pointer(): add use_treshold parameter --- include/drag.h | 20 +++++++++++++------- src/drag.c | 51 ++++++++++++++++++++++++++++++++++++++++++++------ src/floating.c | 10 +++------- src/resize.c | 2 +- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/include/drag.h b/include/drag.h index 924ff98c..2027f934 100644 --- a/include/drag.h +++ b/include/drag.h @@ -12,12 +12,13 @@ #include /** Callback for dragging */ -typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t, const void *); +typedef void (*callback_t)(Con *, Rect *, uint32_t, uint32_t, + const xcb_button_press_event_t *, const void *); /** Macro to create a callback function for dragging */ -#define DRAGGING_CB(name) \ - static void name(Con *con, Rect *old_rect, uint32_t new_x, \ - uint32_t new_y, const void *extra) +#define DRAGGING_CB(name) \ + static void name(Con *con, Rect *old_rect, uint32_t new_x, uint32_t new_y, \ + const xcb_button_press_event_t *event, const void *extra) /** * This is the return value of a drag operation like drag_pointer. @@ -46,10 +47,15 @@ typedef enum { * This function grabs your pointer and keyboard and lets you drag stuff around * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will * be received and the given callback will be called with the parameters - * specified (client, border on which the click originally was), the original - * rect of the client, the event and the new coordinates (x, y). + * specified (client, the original event), the original rect of the client, + * and the new coordinates (x, y). + * + * If use_threshold is set, dragging only starts after the user moves the + * pointer past a certain threshold. That is, the cursor will not be set and the + * callback will not be called until then. * */ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, int cursor, - callback_t callback, const void *extra); + bool use_threshold, callback_t callback, + const void *extra); diff --git a/src/drag.c b/src/drag.c index 57d9f9a9..6b05311a 100644 --- a/src/drag.c +++ b/src/drag.c @@ -20,16 +20,32 @@ struct drag_x11_cb { * drag of the resize handle. */ Con *con; + /* The original event that initiated the drag. */ + const xcb_button_press_event_t *event; + /* The dimensions of con when the loop was started. */ Rect old_rect; /* The callback to invoke after every pointer movement. */ callback_t callback; + /* Drag distance threshold exceeded. If use_threshold is not set, then + * threshold_exceeded is always true. */ + bool threshold_exceeded; + + /* Cursor to set after the threshold is exceeded. */ + xcb_cursor_t xcursor; + /* User data pointer for callback. */ const void *extra; }; +static bool threshold_exceeded(uint32_t x1, uint32_t y1, + uint32_t x2, uint32_t y2) { + const uint32_t threshold = 9; + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold; +} + static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) { xcb_motion_notify_event_t *last_motion_notify = NULL; xcb_generic_event_t *event; @@ -105,15 +121,29 @@ static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) { return true; } + if (!dragloop->threshold_exceeded && + threshold_exceeded(last_motion_notify->root_x, last_motion_notify->root_y, + dragloop->event->root_x, dragloop->event->root_y)) { + if (dragloop->xcursor != XCB_NONE) { + xcb_change_active_pointer_grab( + conn, + dragloop->xcursor, + XCB_CURRENT_TIME, + XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION); + } + dragloop->threshold_exceeded = true; + } + /* 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)) { + if (dragloop->threshold_exceeded && (!dragloop->con || con_exists(dragloop->con))) { dragloop->callback( dragloop->con, &(dragloop->old_rect), last_motion_notify->root_x, last_motion_notify->root_y, + dragloop->event, dragloop->extra); } FREE(last_motion_notify); @@ -133,12 +163,18 @@ static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { * This function grabs your pointer and keyboard and lets you drag stuff around * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will * be received and the given callback will be called with the parameters - * specified (client, border on which the click originally was), the original - * rect of the client, the event and the new coordinates (x, y). + * specified (client, the original event), the original rect of the client, + * and the new coordinates (x, y). + * + * If use_threshold is set, dragging only starts after the user moves the + * pointer past a certain threshold. That is, the cursor will not be set and the + * callback will not be called until then. * */ -drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, - int cursor, callback_t callback, const void *extra) { +drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, + xcb_window_t confine_to, int cursor, + bool use_threshold, callback_t callback, + const void *extra) { xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE; /* Grab the pointer */ @@ -153,7 +189,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ XCB_GRAB_MODE_ASYNC, /* keyboard mode */ confine_to, /* confine_to = in which window should the cursor stay */ - xcursor, /* possibly display a special cursor */ + use_threshold ? XCB_NONE : xcursor, /* possibly display a special cursor */ XCB_CURRENT_TIME); if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) { @@ -189,7 +225,10 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ struct drag_x11_cb loop = { .result = DRAGGING, .con = con, + .event = event, .callback = callback, + .threshold_exceeded = !use_threshold, + .xcursor = xcursor, .extra = extra, }; ev_prepare *prepare = &loop.prepare; diff --git a/src/floating.c b/src/floating.c index 6772128a..59c8c797 100644 --- a/src/floating.c +++ b/src/floating.c @@ -561,8 +561,6 @@ void floating_move_to_pointer(Con *con) { } DRAGGING_CB(drag_window_callback) { - const struct xcb_button_press_event_t *event = extra; - /* Reposition the client correctly while moving */ con->rect.x = old_rect->x + (new_x - event->root_x); con->rect.y = old_rect->y + (new_y - event->root_y); @@ -595,7 +593,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { Rect initial_rect = con->rect; /* Drag the window */ - drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, drag_window_callback, event); + drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, false, drag_window_callback, NULL); if (!con_exists(con)) { DLOG("The container has been closed in the meantime.\n"); @@ -625,12 +623,10 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { struct resize_window_callback_params { const border_t corner; const bool proportional; - const xcb_button_press_event_t *event; }; DRAGGING_CB(resize_window_callback) { const struct resize_window_callback_params *params = extra; - const xcb_button_press_event_t *event = params->event; border_t corner = params->corner; int32_t dest_x = con->rect.x; @@ -706,12 +702,12 @@ void floating_resize_window(Con *con, const bool proportional, cursor = (corner & BORDER_LEFT) ? XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER; } - struct resize_window_callback_params params = {corner, proportional, event}; + struct resize_window_callback_params params = {corner, proportional}; /* get the initial rect in case of revert/cancel */ Rect initial_rect = con->rect; - drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, resize_window_callback, ¶ms); + drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, cursor, false, resize_window_callback, ¶ms); if (!con_exists(con)) { DLOG("The container has been closed in the meantime.\n"); diff --git a/src/resize.c b/src/resize.c index a63e337b..f3c1f678 100644 --- a/src/resize.c +++ b/src/resize.c @@ -208,7 +208,7 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation const struct callback_params params = {orientation, output, helpwin, &new_position}; /* `drag_pointer' blocks until the drag is completed. */ - drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, resize_callback, ¶ms); + drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, false, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); From c6b56b09ab83477dc551f3411462359a2461af2e Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Wed, 12 Dec 2018 18:24:03 +0700 Subject: [PATCH 25/76] Floating move drag: use threshold --- include/floating.h | 2 +- src/click.c | 8 ++++---- src/floating.c | 4 ++-- src/handlers.c | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/floating.h b/include/floating.h index 4b66eef7..4af434cf 100644 --- a/include/floating.h +++ b/include/floating.h @@ -75,7 +75,7 @@ void floating_move_to_pointer(Con *con); * Calls the drag_pointer function with the drag_window callback * */ -void floating_drag_window(Con *con, const xcb_button_press_event_t *event); +void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold); /** * Called when the user clicked on a floating window while holding the diff --git a/src/click.c b/src/click.c index 58ebbf3d..4fcc22ec 100644 --- a/src/click.c +++ b/src/click.c @@ -221,6 +221,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod Con *floatingcon = con_inside_floating(con); const bool proportional = (event->state & XCB_KEY_BUT_MASK_SHIFT) == XCB_KEY_BUT_MASK_SHIFT; const bool in_stacked = (con->parent->layout == L_STACKED || con->parent->layout == L_TABBED); + const bool was_focused = focused == con; /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ if (in_stacked && @@ -258,7 +259,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (floatingcon != NULL && fs != con) { /* 4: floating_modifier plus left mouse button drags */ if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) { - floating_drag_window(floatingcon, event); + floating_drag_window(floatingcon, event, false); return 1; } @@ -293,9 +294,8 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 6: dragging, if this was a click on a decoration (which did not lead * to a resize) */ - if (!in_stacked && dest == CLICK_DECORATION && - (event->detail == XCB_BUTTON_CLICK_LEFT)) { - floating_drag_window(floatingcon, event); + if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_LEFT) { + floating_drag_window(floatingcon, event, !was_focused); return 1; } diff --git a/src/floating.c b/src/floating.c index 59c8c797..719cc194 100644 --- a/src/floating.c +++ b/src/floating.c @@ -582,7 +582,7 @@ DRAGGING_CB(drag_window_callback) { * Calls the drag_pointer function with the drag_window callback * */ -void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { +void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool use_threshold) { DLOG("floating_drag_window\n"); /* Push changes before dragging, so that the window gets raised now and not @@ -593,7 +593,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { Rect initial_rect = con->rect; /* Drag the window */ - drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, false, drag_window_callback, NULL); + drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_window_callback, NULL); if (!con_exists(con)) { DLOG("The container has been closed in the meantime.\n"); diff --git a/src/handlers.c b/src/handlers.c index 7a334978..e177c8fa 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -913,7 +913,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { .event_y = y_root - (con->rect.y)}; switch (direction) { case _NET_WM_MOVERESIZE_MOVE: - floating_drag_window(con->parent, &fake); + floating_drag_window(con->parent, &fake, false); break; case _NET_WM_MOVERESIZE_SIZE_TOPLEFT ... _NET_WM_MOVERESIZE_SIZE_LEFT: floating_resize_window(con->parent, false, &fake); From 371dc23101ac0a46646259da608a754ec930748d Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Wed, 12 Dec 2018 20:53:53 +0700 Subject: [PATCH 26/76] Tiling resize drag: use threshold --- include/resize.h | 4 +++- src/click.c | 49 +++++++++++++++++------------------------------- src/resize.c | 47 ++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/include/resize.h b/include/resize.h index 162d8f6b..5439fab5 100644 --- a/include/resize.h +++ b/include/resize.h @@ -13,7 +13,9 @@ bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction, bool both_sides); -void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event); +void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, + const xcb_button_press_event_t *event, + bool use_threshold); /** * Resize the two given containers using the given amount of pixels or diff --git a/src/click.c b/src/click.c index 4fcc22ec..8ab5c5f0 100644 --- a/src/click.c +++ b/src/click.c @@ -26,7 +26,7 @@ typedef enum { CLICK_BORDER = 0, * then calls resize_graphical_handler(). * */ -static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) { +static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event, bool use_threshold) { DLOG("border = %d, con = %p\n", border, con); Con *second = NULL; Con *first = con; @@ -64,7 +64,7 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT); - resize_graphical_handler(first, second, orientation, event); + resize_graphical_handler(first, second, orientation, event, use_threshold); DLOG("After resize handler, rendering\n"); tree_render(); @@ -94,22 +94,22 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve if (to_right < to_left && to_right < to_top && to_right < to_bottom) - return tiling_resize_for_border(con, BORDER_RIGHT, event); + return tiling_resize_for_border(con, BORDER_RIGHT, event, false); if (to_left < to_right && to_left < to_top && to_left < to_bottom) - return tiling_resize_for_border(con, BORDER_LEFT, event); + return tiling_resize_for_border(con, BORDER_LEFT, event, false); if (to_top < to_right && to_top < to_left && to_top < to_bottom) - return tiling_resize_for_border(con, BORDER_TOP, event); + return tiling_resize_for_border(con, BORDER_TOP, event, false); if (to_bottom < to_right && to_bottom < to_left && to_bottom < to_top) - return tiling_resize_for_border(con, BORDER_BOTTOM, event); + return tiling_resize_for_border(con, BORDER_BOTTOM, event, false); return false; } @@ -118,45 +118,26 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve * Finds out which border was clicked on and calls tiling_resize_for_border(). * */ -static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest) { +static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click_destination_t dest, bool use_threshold) { /* check if this was a click on the window border (and on which one) */ Rect bsr = con_border_style_rect(con); DLOG("BORDER x = %d, y = %d for con %p, window 0x%08x\n", event->event_x, event->event_y, con, event->event); DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); if (dest == CLICK_DECORATION) { - /* The user clicked on a window decoration. We ignore the following case: - * The container is a h-split, tabbed or stacked container with > 1 - * window. Decorations will end up next to each other and the user - * expects to switch to a window by clicking on its decoration. */ - - /* Since the container might either be the child *or* already a split - * container (in the case of a nested split container), we need to make - * sure that we are dealing with the split container here. */ - Con *check_con = con; - if (con_is_leaf(check_con) && check_con->parent->type == CT_CON) - check_con = check_con->parent; - - if ((check_con->layout == L_STACKED || - check_con->layout == L_TABBED || - con_orientation(check_con) == HORIZ) && - con_num_children(check_con) > 1) { - DLOG("Not handling this resize, this container has > 1 child.\n"); - return false; - } - return tiling_resize_for_border(con, BORDER_TOP, event); + return tiling_resize_for_border(con, BORDER_TOP, event, use_threshold); } if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x && event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height)) - return tiling_resize_for_border(con, BORDER_LEFT, event); + return tiling_resize_for_border(con, BORDER_LEFT, event, false); if (event->event_x >= (int32_t)(con->window_rect.x + con->window_rect.width) && event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height)) - return tiling_resize_for_border(con, BORDER_RIGHT, event); + return tiling_resize_for_border(con, BORDER_RIGHT, event, false); if (event->event_y >= (int32_t)(con->window_rect.y + con->window_rect.height)) - return tiling_resize_for_border(con, BORDER_BOTTOM, event); + return tiling_resize_for_border(con, BORDER_BOTTOM, event, false); return false; } @@ -276,7 +257,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod is_left_or_right_click) { /* try tiling resize, but continue if it doesn’t work */ DLOG("tiling resize with fallback\n"); - if (tiling_resize(con, event, dest)) + if (tiling_resize(con, event, dest, !was_focused)) goto done; } @@ -311,7 +292,11 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod else if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) && is_left_or_right_click) { DLOG("Trying to resize (tiling)\n"); - tiling_resize(con, event, dest); + /* Since we updated the tree (con_activate() above), we need to + * re-render the tree before returning to the event loop (drag_pointer() + * inside tiling_resize() runs its own event-loop). */ + tree_render(); + tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused); } done: diff --git a/src/resize.c b/src/resize.c index f3c1f678..97a0f946 100644 --- a/src/resize.c +++ b/src/resize.c @@ -21,12 +21,32 @@ struct callback_params { Con *output; xcb_window_t helpwin; uint32_t *new_position; + bool *threshold_exceeded; }; DRAGGING_CB(resize_callback) { const struct callback_params *params = extra; Con *output = params->output; DLOG("new x = %d, y = %d\n", new_x, new_y); + + if (!*params->threshold_exceeded) { + xcb_map_window(conn, params->helpwin); + /* Warp pointer in the same way as resize_graphical_handler() would do + * if threshold wasn't enabled, but also take into account travelled + * distance. */ + if (params->orientation == HORIZ) { + xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0, + *params->new_position + new_x - event->root_x, + new_y); + } else { + xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0, + new_x, + *params->new_position + new_y - event->root_y); + } + *params->threshold_exceeded = true; + return; + } + if (params->orientation == HORIZ) { /* Check if the new coordinates are within screen boundaries */ if (new_x > (output->rect.x + output->rect.width - 25) || @@ -148,7 +168,9 @@ bool resize_neighboring_cons(Con *first, Con *second, int px, int ppt) { return true; } -void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) { +void resize_graphical_handler(Con *first, Con *second, orientation_t orientation, + const xcb_button_press_event_t *event, + bool use_threshold) { Con *output = con_get_output(first); DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width); @@ -179,14 +201,10 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation helprect.width = logical_px(2); helprect.height = second->rect.height; initial_position = second->rect.x; - xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0, - second->rect.x, event->root_y); } else { helprect.width = second->rect.width; helprect.height = logical_px(2); initial_position = second->rect.y; - xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0, - event->root_x, second->rect.y); } mask = XCB_CW_BACK_PIXEL; @@ -196,7 +214,18 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation values[1] = 1; xcb_window_t helpwin = create_window(conn, helprect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, - XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), true, mask, values); + XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == HORIZ ? XCURSOR_CURSOR_RESIZE_HORIZONTAL : XCURSOR_CURSOR_RESIZE_VERTICAL), false, mask, values); + + if (!use_threshold) { + xcb_map_window(conn, helpwin); + if (orientation == HORIZ) { + xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0, + second->rect.x, event->root_y); + } else { + xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0, + event->root_x, second->rect.y); + } + } xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); @@ -205,10 +234,12 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation /* `new_position' will be updated by the `resize_callback'. */ new_position = initial_position; - const struct callback_params params = {orientation, output, helpwin, &new_position}; + bool threshold_exceeded = !use_threshold; + + const struct callback_params params = {orientation, output, helpwin, &new_position, &threshold_exceeded}; /* `drag_pointer' blocks until the drag is completed. */ - drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, false, resize_callback, ¶ms); + drag_result_t drag_result = drag_pointer(NULL, event, grabwin, 0, use_threshold, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); From beb96ad18cfad44e54643af1af0f9900216e8b85 Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Mon, 7 Oct 2019 06:13:43 +0000 Subject: [PATCH 27/76] Move container to marked workspace: refine corner case This commit should fix "move con to parent" trick (see below) in the case when con->parent->parent is a workspace. The trick: mark _a, focus parent, focus parent, mark _b, [con_mark=_a] move window to mark _b, [con_mark=_a] focus The trick got broken in commit 626af81232e6ca81abea22267f3330c16d804596 in order to fix an i3 crash (#2003). Reverting said commit fixes the trick. The crash is caused by the fact that empty workspace isn't considered a split (checked in src/con.c:1324), so the moved window ends up as a sibling of the target workspace, not as its child. --- src/con.c | 4 ++-- testcases/lib/i3test.pm.in | 2 +- testcases/t/243-move-to-mark.t | 1 + testcases/t/306-move-to-parent.t | 37 ++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 testcases/t/306-move-to-parent.t diff --git a/src/con.c b/src/con.c index 0f913e0a..e9f82cf4 100644 --- a/src/con.c +++ b/src/con.c @@ -1312,8 +1312,8 @@ bool con_move_to_mark(Con *con, const char *mark) { return true; } - if (target->type == CT_WORKSPACE) { - DLOG("target container is a workspace, simply moving the container there.\n"); + if (target->type == CT_WORKSPACE && con_is_leaf(target)) { + DLOG("target container is an empty workspace, simply moving the container there.\n"); con_move_to_workspace(con, target, true, false, false); return true; } diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 740e13e9..161ddf79 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -1230,7 +1230,7 @@ sub create_layout { $r = $r . '{"swallows": [{'; $r = $r . '"class": "^' . "$char" . '$"'; - $r = $r . '}]},'; + $r = $r . '}]}' . ($depth == 0 ? "\n" : ','); } else { die "Could not understand $char"; } diff --git a/testcases/t/243-move-to-mark.t b/testcases/t/243-move-to-mark.t index b6ec462e..390da04d 100644 --- a/testcases/t/243-move-to-mark.t +++ b/testcases/t/243-move-to-mark.t @@ -361,6 +361,7 @@ does_i3_live; ############################################################################### # Given 'S' and 'M' where 'M' is a workspace and 'S' is on a different # workspace, then 'S' ends up as a tiling container on 'M'. +# See issue: #2003 ############################################################################### fresh_workspace; diff --git a/testcases/t/306-move-to-parent.t b/testcases/t/306-move-to-parent.t new file mode 100644 index 00000000..8610cff9 --- /dev/null +++ b/testcases/t/306-move-to-parent.t @@ -0,0 +1,37 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Make sure the trick used to move the container to its parent works. +# https://github.com/i3/i3/issues/1326#issuecomment-349082811 +use i3test; + +cmp_tree( + msg => 'Move to parent when the parent is a workspace', + layout_before => 'a H[b*] c', + layout_after => 'a b* c', + cb => sub { + cmd 'mark _a, focus parent, focus parent, mark _b, [con_mark=_a] move window to mark _b, [con_mark=_a] focus'; + }); + +cmp_tree( + msg => 'Move to parent when the parent is a split', + layout_before => 'V[a H[b*] c]', + layout_after => 'V[a b* c]', + cb => sub { + cmd 'mark _a, focus parent, focus parent, mark _b, [con_mark=_a] move window to mark _b, [con_mark=_a] focus'; + }); + +done_testing; From 34c217acc878f200614ea756501863daba6da600 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 9 Oct 2019 02:31:52 +0300 Subject: [PATCH 28/76] Introduce con_activate_unblock --- include/con.h | 7 +++++++ src/commands.c | 36 ++---------------------------------- src/con.c | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/include/con.h b/include/con.h index 2d843eeb..d8330098 100644 --- a/include/con.h +++ b/include/con.h @@ -45,6 +45,13 @@ void con_focus(Con *con); */ void con_activate(Con *con); +/** + * Activates the container like in con_activate but removes fullscreen + * restrictions and properly warps the pointer if needed. + * + */ +void con_activate_unblock(Con *con); + /** * Closes the given container. * diff --git a/src/commands.c b/src/commands.c index 5b11fce1..b4f2ee19 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1238,20 +1238,6 @@ void cmd_focus_direction(I3_CMD, const char *direction) { ysuccess(true); } -/* - * Focus a container and disable any other fullscreen container not permitting the focus. - * - */ -static void cmd_focus_force_focus(Con *con) { - /* Disable fullscreen container in workspace with container to be focused. */ - Con *ws = con_get_workspace(con); - Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws); - if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) { - con_disable_fullscreen(fullscreen_on_ws); - } - con_activate(con); -} - /* * Implementation of 'focus tiling|floating|mode_toggle'. * @@ -1276,7 +1262,7 @@ void cmd_focus_window_mode(I3_CMD, const char *window_mode) { (!to_floating && current->type == CT_FLOATING_CON)) continue; - cmd_focus_force_focus(con_descend_focused(current)); + con_activate_unblock(con_descend_focused(current)); success = true; break; } @@ -1353,26 +1339,8 @@ void cmd_focus(I3_CMD) { break; } - /* If the container is not on the current workspace, - * workspace_show() will switch to a different workspace and (if - * enabled) trigger a mouse pointer warp to the currently focused - * container (!) on the target workspace. - * - * Therefore, before calling workspace_show(), we make sure that - * 'current' will be focused on the workspace. However, we cannot - * just con_focus(current) because then the pointer will not be - * warped at all (the code thinks we are already there). - * - * So we focus 'current' to make it the currently focused window of - * the target workspace, then revert focus. */ - Con *currently_focused = focused; - cmd_focus_force_focus(current->con); - con_activate(currently_focused); - - /* Now switch to the workspace, then focus */ - workspace_show(ws); LOG("focusing %p / %s\n", current->con, current->con->name); - con_activate(current->con); + con_activate_unblock(current->con); count++; } diff --git a/src/con.c b/src/con.c index e9f82cf4..e0993ade 100644 --- a/src/con.c +++ b/src/con.c @@ -265,6 +265,41 @@ void con_activate(Con *con) { con_raise(con); } +/* + * Activates the container like in con_activate but removes fullscreen + * restrictions and properly warps the pointer if needed. + * + */ +void con_activate_unblock(Con *con) { + Con *ws = con_get_workspace(con); + Con *previous_focus = focused; + Con *fullscreen_on_ws = con_get_fullscreen_covering_ws(ws); + + if (fullscreen_on_ws && fullscreen_on_ws != con && !con_has_parent(con, fullscreen_on_ws)) { + con_disable_fullscreen(fullscreen_on_ws); + } + + con_activate(con); + + /* If the container is not on the current workspace, workspace_show() will + * switch to a different workspace and (if enabled) trigger a mouse pointer + * warp to the currently focused container (!) on the target workspace. + * + * Therefore, before calling workspace_show(), we make sure that 'con' will + * be focused on the workspace. However, we cannot just con_focus(con) + * because then the pointer will not be warped at all (the code thinks we + * are already there). + * + * So we focus 'con' to make it the currently focused window of the target + * workspace, then revert focus. */ + if (ws != con_get_workspace(previous_focus)) { + con_activate(previous_focus); + /* Now switch to the workspace, then focus */ + workspace_show(ws); + con_activate(con); + } +} + /* * Closes the given container. * From 6f82d21c39f06c75812e7363d0b21255d1349414 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 9 Oct 2019 02:32:22 +0300 Subject: [PATCH 29/76] handlers.c: new focus should not end up behind fullscreen This was raised here: https://www.reddit.com/r/i3wm/comments/df18aa/popup_during_fullscreen_not_behaving_the_way_i/ With this commit, _NET_ACTIVE_WINDOW requests are more similar to focusing with cmd_focus. --- src/handlers.c | 15 ++++----------- testcases/t/195-net-active-window.t | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 7a334978..3a83d6a4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -412,7 +412,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(workspace))) { DLOG("Focusing con = %p\n", con); workspace_show(workspace); - con_activate(con); + con_activate_unblock(con); tree_render(); } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) { DLOG("Marking con = %p urgent\n", con); @@ -758,7 +758,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { workspace_show(ws); /* Re-set focus, even if unchanged from i3’s perspective. */ focused_id = XCB_NONE; - con_activate(con); + con_activate_unblock(con); } } else { /* Request is from an application. */ @@ -769,8 +769,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { DLOG("Focusing con = %p\n", con); - workspace_show(ws); - con_activate(con); + con_activate_unblock(con); } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { DLOG("Marking con = %p urgent\n", con); con_set_urgency(con, true); @@ -1115,14 +1114,8 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { 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 - * correctly update state and send the IPC event. */ - Con *ws = con_get_workspace(con); - if (ws != con_get_workspace(focused)) - workspace_show(ws); + con_activate_unblock(con); - con_activate(con); /* We update focused_id because we don’t need to set focus again */ focused_id = event->event; tree_render(); diff --git a/testcases/t/195-net-active-window.t b/testcases/t/195-net-active-window.t index f9f883cb..4ce12089 100644 --- a/testcases/t/195-net-active-window.t +++ b/testcases/t/195-net-active-window.t @@ -149,6 +149,22 @@ send_net_active_window($scratch->id, 'pager'); is($x->input_focus, $scratch->id, 'scratchpad window is shown'); +################################################################################ +# Send a _NET_ACTIVE_WINDOW ClientMessage for a window behind a fullscreen +# window +################################################################################ + +$ws1 = fresh_workspace; +$win1 = open_window; +$win2 = open_window; +cmd 'fullscreen enable'; +is_num_fullscreen($ws1, 1, '1 fullscreen window in workspace'); + +send_net_active_window($win1->id); + +is($x->input_focus, $win1->id, 'window behind fullscreen window is now focused'); +is_num_fullscreen($ws1, 0, 'no fullscreen windows in workspace'); + ################################################################################ # Verify that the _NET_ACTIVE_WINDOW property is updated on the root window # correctly. From 7e4eb51d23cb3aaa871d64e002243b97cb82bffc Mon Sep 17 00:00:00 2001 From: Albert Safin Date: Wed, 9 Oct 2019 07:47:51 +0000 Subject: [PATCH 30/76] When renaming a workspace, update the previous_workspace_name too Fixes #3694 --- src/commands.c | 7 +++++ testcases/t/176-workspace-baf.t | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/commands.c b/src/commands.c index 5b11fce1..615238da 100644 --- a/src/commands.c +++ b/src/commands.c @@ -2013,6 +2013,13 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { con_focus(previously_focused); } + /* Let back-and-forth work after renaming the previous workspace. + * See #3694. */ + if (previous_workspace_name && !strcmp(previous_workspace_name, old_name_copy)) { + FREE(previous_workspace_name); + previous_workspace_name = sstrdup(new_name); + } + cmd_output->needs_tree_render = true; ysuccess(true); diff --git a/testcases/t/176-workspace-baf.t b/testcases/t/176-workspace-baf.t index 133e00fc..16738429 100644 --- a/testcases/t/176-workspace-baf.t +++ b/testcases/t/176-workspace-baf.t @@ -171,6 +171,53 @@ cmd 'restart'; cmd 'workspace back_and_forth'; is(focused_ws, '5: foo', 'workspace 5 focused after restart'); +################################################################################ +# Check BAF switching to renamed workspace. +# Issue: #3694 +################################################################################ + +kill_all_windows; +cmd 'workspace --no-auto-back-and-forth 1'; +open_window; +cmd 'workspace --no-auto-back-and-forth 2'; + +cmd 'rename workspace 1 to 3'; +cmd 'workspace back_and_forth'; +is(focused_ws, '3', 'workspace 3 focused after rename'); + +################################################################################ +# Check BAF switching to renamed and then closed workspace. +# Issue: #3694 +################################################################################ + +kill_all_windows; +cmd 'workspace --no-auto-back-and-forth 1'; +$first_win = open_window; +cmd 'workspace --no-auto-back-and-forth 2'; + +cmd 'rename workspace 1 to 3'; +cmd '[id="' . $first_win->id . '"] kill'; + +cmd 'workspace back_and_forth'; +is(focused_ws, '3', 'workspace 3 focused after renaming and destroying'); + +################################################################################ +# See if renaming current workspace doesn't affect BAF switching to another +# renamed workspace. +# Issue: #3694 +################################################################################ + +kill_all_windows; +cmd 'workspace --no-auto-back-and-forth 1'; +$first_win = open_window; +cmd 'workspace --no-auto-back-and-forth 2'; + +cmd 'rename workspace 1 to 3'; +cmd 'rename workspace 2 to 4'; + +cmd 'workspace back_and_forth'; +is(focused_ws, '3', 'workspace 3 focused after renaming'); + exit_gracefully($pid); done_testing; From 961f98eb15ddbb6b34f612c3444068d06ac25c4b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Wed, 9 Oct 2019 16:08:18 +0300 Subject: [PATCH 31/76] Remove outdated comment from Rect This has changed after #3787. The packed attribute was added in 75aac5bc02f07f1b1b1814e488926d22173e3594 for _NET_WORKAREA. However, eec80838ab39377f18e3f68dc8a27923ae19e57e removed _NET_WORKAREA support. I did some quick greping for `memcpy.+Rect` and didn't find any similar code that could theoretically lead to problems. --- include/data.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/data.h b/include/data.h index 82027f75..8cf38790 100644 --- a/include/data.h +++ b/include/data.h @@ -144,8 +144,6 @@ typedef enum { /** * Stores a rectangle, for example the size of a window, the child window etc. - * It needs to be packed so that the compiler will not add any padding bytes. - * (it is used in src/ewmh.c for example) * * Note that x and y can contain signed values in some cases (for example when * used for the coordinates of a window, which can be set outside of the From d5a3b8af021c74e4abf05db9578283164a2feabc Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 11 Oct 2019 16:10:32 +0300 Subject: [PATCH 32/76] Introduce trayclient_and_output_from_window Saves some code repetition --- i3bar/src/xcb.c | 148 ++++++++++++++++++++++-------------------------- 1 file changed, 68 insertions(+), 80 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index da95a5dc..30ce06b7 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -722,6 +722,30 @@ static void configure_trayclients(void) { } } +static trayclient *trayclient_and_output_from_window(xcb_window_t win, i3_output **output) { + i3_output *o_walk; + SLIST_FOREACH(o_walk, outputs, slist) { + if (!o_walk->active) { + continue; + } + + trayclient *client; + TAILQ_FOREACH(client, o_walk->trayclients, tailq) { + if (client->win == win) { + if (output) { + *output = o_walk; + } + return client; + } + } + } + return NULL; +} + +static trayclient *trayclient_from_window(xcb_window_t win) { + return trayclient_and_output_from_window(win, NULL); +} + /* * Handles ClientMessages (messages sent from another client directly to us). * @@ -884,27 +908,20 @@ static void handle_client_message(xcb_client_message_event_t *event) { static void handle_destroy_notify(xcb_destroy_notify_event_t *event) { DLOG("DestroyNotify for window = %08x, event = %08x\n", event->window, event->event); - i3_output *walk; - SLIST_FOREACH(walk, outputs, slist) { - if (!walk->active) - continue; - DLOG("checking output %s\n", walk->name); - trayclient *trayclient; - TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { - if (trayclient->win != event->window) { - continue; - } - - DLOG("Removing tray client with window ID %08x\n", event->window); - TAILQ_REMOVE(walk->trayclients, trayclient, tailq); - FREE(trayclient); - - /* Trigger an update, we now have more space for the statusline */ - configure_trayclients(); - draw_bars(false); - return; - } + i3_output *output; + trayclient *client = trayclient_and_output_from_window(event->window, &output); + if (!client) { + DLOG("WARNING: Could not find corresponding tray window.\n"); + return; } + + DLOG("Removing tray client with window ID %08x\n", event->window); + TAILQ_REMOVE(output->trayclients, client, tailq); + FREE(client); + + /* Trigger an update, we now have more space for the statusline */ + configure_trayclients(); + draw_bars(false); } /* @@ -915,25 +932,18 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) { static void handle_map_notify(xcb_map_notify_event_t *event) { DLOG("MapNotify for window = %08x, event = %08x\n", event->window, event->event); - i3_output *walk; - SLIST_FOREACH(walk, outputs, slist) { - if (!walk->active) - continue; - DLOG("checking output %s\n", walk->name); - trayclient *trayclient; - TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { - if (trayclient->win != event->window) - continue; - - DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window); - trayclient->mapped = true; - - /* Trigger an update, we now have more space for the statusline */ - configure_trayclients(); - draw_bars(false); - return; - } + trayclient *client = trayclient_from_window(event->window); + if (!client) { + DLOG("WARNING: Could not find corresponding tray window.\n"); + return; } + + DLOG("Tray client mapped (window ID %08x). Adjusting tray.\n", event->window); + client->mapped = true; + + /* Trigger an update, we now have one extra tray client. */ + configure_trayclients(); + draw_bars(false); } /* * Handles UnmapNotify events. These events happen when a tray client hides its @@ -943,25 +953,18 @@ static void handle_map_notify(xcb_map_notify_event_t *event) { static void handle_unmap_notify(xcb_unmap_notify_event_t *event) { DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event); - i3_output *walk; - SLIST_FOREACH(walk, outputs, slist) { - if (!walk->active) - continue; - DLOG("checking output %s\n", walk->name); - trayclient *trayclient; - TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { - if (trayclient->win != event->window) - continue; - - DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window); - trayclient->mapped = false; - - /* Trigger an update, we now have more space for the statusline */ - configure_trayclients(); - draw_bars(false); - return; - } + trayclient *client = trayclient_from_window(event->window); + if (!client) { + DLOG("WARNING: Could not find corresponding tray window.\n"); + return; } + + DLOG("Tray client unmapped (window ID %08x). Adjusting tray.\n", event->window); + client->mapped = false; + + /* Trigger an update, we now have more space for the statusline */ + configure_trayclients(); + draw_bars(false); } /* @@ -974,31 +977,16 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { if (event->atom == atoms[_XEMBED_INFO] && event->state == XCB_PROPERTY_NEW_VALUE) { DLOG("xembed_info updated\n"); - trayclient *trayclient = NULL, *walk; - i3_output *o_walk; - SLIST_FOREACH(o_walk, outputs, slist) { - if (!o_walk->active) - continue; - - TAILQ_FOREACH(walk, o_walk->trayclients, tailq) { - if (walk->win != event->window) - continue; - trayclient = walk; - break; - } - - if (trayclient) - break; - } - if (!trayclient) { - ELOG("PropertyNotify received for unknown window %08x\n", - event->window); + trayclient *client = trayclient_from_window(event->window); + if (!client) { + ELOG("PropertyNotify received for unknown window %08x\n", event->window); return; } + xcb_get_property_cookie_t xembedc; xembedc = xcb_get_property_unchecked(xcb_connection, 0, - trayclient->win, + client->win, atoms[_XEMBED_INFO], XCB_GET_PROPERTY_TYPE_ANY, 0, @@ -1018,12 +1006,12 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { DLOG("xembed flags = %d\n", xembed[1]); bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED); DLOG("map state now %d\n", map_it); - if (trayclient->mapped && !map_it) { + if (client->mapped && !map_it) { /* need to unmap the window */ - xcb_unmap_window(xcb_connection, trayclient->win); - } else if (!trayclient->mapped && map_it) { + xcb_unmap_window(xcb_connection, client->win); + } else if (!client->mapped && map_it) { /* need to map the window */ - xcb_map_window(xcb_connection, trayclient->win); + xcb_map_window(xcb_connection, client->win); } free(xembedr); } From eda814755afd8c078c543c486927c45a8ada7d6f Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 11 Oct 2019 17:04:10 +0300 Subject: [PATCH 33/76] Make tray icon order deterministic Fixes #3573 --- i3bar/include/trayclients.h | 3 + i3bar/src/xcb.c | 118 +++++++++++++++++++++++++++++++----- 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h index db954bb1..3f215ce4 100644 --- a/i3bar/include/trayclients.h +++ b/i3bar/include/trayclients.h @@ -18,6 +18,9 @@ struct trayclient { bool mapped; /* Whether this window is mapped */ int xe_version; /* The XEMBED version supported by the client */ + char *class_class; + char *class_instance; + TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 30ce06b7..26ff3ded 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -692,6 +692,31 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) { } } +static int strcasecmp_nullable(const char *a, const char *b) { + if (a == b) { + return 0; + } + if (a == NULL) { + return -1; + } + if (b == NULL) { + return 1; + } + return strcasecmp(a, b); +} + +/* + * Sort trayclients in descending order + * + */ +static int reorder_trayclients_cmp(const void *_a, const void *_b) { + trayclient *a = *((trayclient **)_a); + trayclient *b = *((trayclient **)_b); + + int result = strcasecmp_nullable(a->class_class, b->class_class); + return result != 0 ? result : strcasecmp_nullable(a->class_instance, b->class_instance); +} + /* * Adjusts the size of the tray window and alignment of the tray clients by * configuring their respective x coordinates. To be called when mapping or @@ -699,26 +724,42 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) { * */ static void configure_trayclients(void) { - trayclient *trayclient; i3_output *output; SLIST_FOREACH(output, outputs, slist) { - if (!output->active) + if (!output->active) { continue; + } - int clients = 0; - TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) { - if (!trayclient->mapped) - continue; - clients++; + int count = 0; + trayclient *client; + TAILQ_FOREACH(client, output->trayclients, tailq) { + if (client->mapped) { + count++; + } + } - DLOG("Configuring tray window %08x to x=%d\n", - trayclient->win, output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)))); - uint32_t x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding))); + int idx = 0; + trayclient **trayclients = smalloc(count * sizeof(trayclient *)); + TAILQ_FOREACH(client, output->trayclients, tailq) { + if (client->mapped) { + trayclients[idx++] = client; + } + } + + qsort(trayclients, count, sizeof(trayclient *), reorder_trayclients_cmp); + + uint32_t x = output->rect.w; + for (idx = count; idx > 0; idx--) { + x -= icon_size + logical_px(config.tray_padding); + + DLOG("Configuring tray window %08x to x=%d\n", trayclients[idx - 1]->win, x); xcb_configure_window(xcb_connection, - trayclient->win, + trayclients[idx - 1]->win, XCB_CONFIG_WINDOW_X, &x); } + + free(trayclients); } } @@ -746,6 +787,45 @@ static trayclient *trayclient_from_window(xcb_window_t win) { return trayclient_and_output_from_window(win, NULL); } +static void trayclient_update_class(trayclient *client) { + xcb_get_property_reply_t *prop = xcb_get_property_reply( + conn, + xcb_get_property_unchecked( + xcb_connection, + false, + client->win, + XCB_ATOM_WM_CLASS, + XCB_ATOM_STRING, + 0, + 32), + NULL); + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("WM_CLASS not set.\n"); + free(prop); + return; + } + + /* We cannot use asprintf here since this property contains two + * null-terminated strings (for compatibility reasons). Instead, we + * use strdup() on both strings */ + const size_t prop_length = xcb_get_property_value_length(prop); + char *new_class = xcb_get_property_value(prop); + const size_t class_class_index = strnlen(new_class, prop_length) + 1; + + free(client->class_instance); + free(client->class_class); + + client->class_instance = sstrndup(new_class, prop_length); + if (class_class_index < prop_length) { + client->class_class = sstrndup(new_class + class_class_index, prop_length - class_class_index); + } else { + client->class_class = NULL; + } + DLOG("WM_CLASS changed to %s (instance), %s (class)\n", client->class_instance, client->class_class); + + free(prop); +} + /* * Handles ClientMessages (messages sent from another client directly to us). * @@ -876,11 +956,12 @@ static void handle_client_message(xcb_client_message_event_t *event) { * exits/crashes. */ xcb_change_save_set(xcb_connection, XCB_SET_MODE_INSERT, client); - trayclient *tc = smalloc(sizeof(trayclient)); + trayclient *tc = scalloc(1, sizeof(trayclient)); tc->win = client; tc->xe_version = xe_version; tc->mapped = false; TAILQ_INSERT_TAIL(output_for_tray->trayclients, tc, tailq); + trayclient_update_class(tc); if (map_it) { DLOG("Mapping dock client\n"); @@ -968,15 +1049,16 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t *event) { } /* - * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is - * handled, which tells us whether a dock client should be mapped or unmapped. + * Handle PropertyNotify messages. * */ static void handle_property_notify(xcb_property_notify_event_t *event) { DLOG("PropertyNotify\n"); if (event->atom == atoms[_XEMBED_INFO] && event->state == XCB_PROPERTY_NEW_VALUE) { + /* _XEMBED_INFO property tells us whether a dock client should be mapped or unmapped. */ DLOG("xembed_info updated\n"); + trayclient *client = trayclient_from_window(event->window); if (!client) { ELOG("PropertyNotify received for unknown window %08x\n", event->window); @@ -1014,6 +1096,11 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { xcb_map_window(xcb_connection, client->win); } free(xembedr); + } else if (event->atom == XCB_ATOM_WM_CLASS) { + trayclient *client = trayclient_from_window(event->window); + if (client) { + trayclient_update_class(client); + } } } @@ -1532,6 +1619,9 @@ void kick_tray_clients(i3_output *output) { 0, 0); + free(trayclient->class_class); + free(trayclient->class_instance); + /* We remove the trayclient right here. We might receive an UnmapNotify * event afterwards, but better safe than sorry. */ TAILQ_REMOVE(output->trayclients, trayclient, tailq); From 98d96c3a2c09f7cc8636dd379acbb12ba764980c Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 11 Oct 2019 19:32:00 +0300 Subject: [PATCH 34/76] reorder_trayclients_cmp: Correct comment --- i3bar/src/xcb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 26ff3ded..ad9745fb 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -706,7 +706,8 @@ static int strcasecmp_nullable(const char *a, const char *b) { } /* - * Sort trayclients in descending order + * Comparison function to sort trayclients in ascending alphanumeric order + * according to their class. * */ static int reorder_trayclients_cmp(const void *_a, const void *_b) { From 70c850ac136980700e2cec4960c4ec28d1235e6f Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 13 Oct 2019 13:10:06 +0300 Subject: [PATCH 35/76] Remove memmem implementation Not used after 3bd5e6e5c81b448f9f0ac84454671a871fbfea66 --- include/util.h | 11 ----------- src/util.c | 36 ------------------------------------ 2 files changed, 47 deletions(-) diff --git a/include/util.h b/include/util.h index 7a2b3083..b7bace58 100644 --- a/include/util.h +++ b/include/util.h @@ -124,17 +124,6 @@ bool path_exists(const char *path); */ void i3_restart(bool forget_layout); -#if defined(__OpenBSD__) || defined(__APPLE__) - -/** - * Taken from FreeBSD - * Find the first occurrence of the byte string s in byte string l. - * - */ -void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); - -#endif - /** * Escapes the given string if a pango font is currently used. * If the string has to be escaped, the input string will be free'd. diff --git a/src/util.c b/src/util.c index 812aad37..c3dae6a5 100644 --- a/src/util.c +++ b/src/util.c @@ -312,42 +312,6 @@ void i3_restart(bool forget_layout) { /* not reached */ } -#if defined(__OpenBSD__) || defined(__APPLE__) - -/* - * Taken from FreeBSD - * Find the first occurrence of the byte string s in byte string l. - * - */ -void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) { - register char *cur, *last; - const char *cl = (const char *)l; - const char *cs = (const char *)s; - - /* we need something to compare */ - if (l_len == 0 || s_len == 0) - return NULL; - - /* "s" must be smaller or equal to "l" */ - if (l_len < s_len) - return NULL; - - /* special case where s_len == 1 */ - if (s_len == 1) - return memchr(l, (int)*cs, l_len); - - /* the last position where its possible to find "s" in "l" */ - last = (char *)cl + l_len - s_len; - - for (cur = (char *)cl; cur <= last; cur++) - if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) - return cur; - - return NULL; -} - -#endif - /* * Escapes the given string if a pango font is currently used. * If the string has to be escaped, the input string will be free'd. From b0d6f44779a9e8d8ed2bb0751d3af3bfd484f1a0 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sun, 13 Oct 2019 13:12:14 +0300 Subject: [PATCH 36/76] Use AC_REPLACE_FUNCS strndup is removed from AC_CHECK_FUNCS since it will be provided if not found. Fixes #2610 --- configure.ac | 3 ++- include/libi3.h | 6 ++---- libi3/mkdirp.c | 3 +-- libi3/strndup.c | 4 +--- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/configure.ac b/configure.ac index 22e27f6f..82f6fccd 100644 --- a/configure.ac +++ b/configure.ac @@ -72,7 +72,8 @@ AC_CHECK_TYPES([mode_t, off_t, pid_t, size_t, ssize_t], , [AC_MSG_FAILURE([canno AC_FUNC_FORK AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK AC_FUNC_STRNLEN -AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strndup strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3 requires])]) +AC_CHECK_FUNCS([atexit dup2 ftruncate getcwd gettimeofday localtime_r memchr memset mkdir rmdir setlocale socket strcasecmp strchr strdup strerror strncasecmp strrchr strspn strstr strtol strtoul], , [AC_MSG_FAILURE([cannot find the $ac_func function, which i3 requires])]) +AC_REPLACE_FUNCS([mkdirp strndup]) # Checks for libraries. diff --git a/include/libi3.h b/include/libi3.h index 790baba9..15d1bc76 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -341,8 +341,7 @@ gchar *g_utf8_make_valid(const gchar *str, gssize len); */ uint32_t get_colorpixel(const char *hex) __attribute__((const)); -#if defined(__APPLE__) - +#ifndef HAVE_strndup /** * Taken from FreeBSD * Returns a pointer to a new string which is a duplicate of the @@ -350,7 +349,6 @@ uint32_t get_colorpixel(const char *hex) __attribute__((const)); * */ char *strndup(const char *str, size_t n); - #endif /** @@ -528,7 +526,7 @@ char *resolve_tilde(const char *path); */ char *get_config_path(const char *override_configpath, bool use_system_paths); -#if !defined(__sun) +#ifndef HAVE_mkdirp /** * Emulates mkdir -p (creates any missing folders) * diff --git a/libi3/mkdirp.c b/libi3/mkdirp.c index f5281bd7..35a30475 100644 --- a/libi3/mkdirp.c +++ b/libi3/mkdirp.c @@ -12,12 +12,11 @@ #include #include +#ifndef HAVE_mkdirp /* * Emulates mkdir -p (creates any missing folders) * */ - -#if !defined(__sun) int mkdirp(const char *path, mode_t mode) { if (mkdir(path, mode) == 0) return 0; diff --git a/libi3/strndup.c b/libi3/strndup.c index e17f843c..e215a76f 100644 --- a/libi3/strndup.c +++ b/libi3/strndup.c @@ -10,8 +10,7 @@ #include #include -#if defined(__APPLE__) - +#ifndef HAVE_strndup /* * Taken from FreeBSD * Returns a pointer to a new string which is a duplicate of the @@ -30,5 +29,4 @@ char *strndup(const char *str, size_t n) { copy[len] = '\0'; return (copy); } - #endif From 0cd7250f4177ac192f4b97b9ddfab96cbf2e6366 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 14 Oct 2019 02:28:09 +0300 Subject: [PATCH 37/76] Add testcases/t/308-focus_wrapping.t These tests pass with and without the following refactoring. --- testcases/t/308-focus_wrapping.t | 255 +++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 testcases/t/308-focus_wrapping.t diff --git a/testcases/t/308-focus_wrapping.t b/testcases/t/308-focus_wrapping.t new file mode 100644 index 00000000..7053b5ae --- /dev/null +++ b/testcases/t/308-focus_wrapping.t @@ -0,0 +1,255 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests focus_wrapping yes|no|force|workspace with cmp_tree +# Tickets: #2352 +use i3test i3_autostart => 0; + +my $pid = 0; +sub focus_wrapping { + my ($setting) = @_; + + print "--------------------------------------------------------------------------------\n"; + print " focus_wrapping $setting\n"; + print "--------------------------------------------------------------------------------\n"; + exit_gracefully($pid) if $pid > 0; + + my $config = <<"EOT"; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768 +workspace left-top output fake-0 +workspace right-top output fake-1 +workspace right-bottom output fake-2 +workspace left-bottom output fake-3 + +focus_wrapping $setting +EOT + $pid = launch_with_config($config); +} + +############################################################################### +focus_wrapping('yes'); +############################################################################### + +cmp_tree( + msg => 'Normal focus up - should work for all options', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a* b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Normal focus right - should work for all options', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b] V[c d T[e f* g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + }); +cmp_tree( + msg => 'Focus leaves workspace vertically', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus down'; + is(focused_ws, 'left-bottom', 'Correct workspace focused'); + }); +cmp_tree( + msg => 'Focus wraps vertically', + layout_before => 'S[a* b] V[c d T[e f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Focus wraps horizontally', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g*]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( + msg => 'Directional focus in the orientation of the parent does not wrap', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( + msg => 'Focus leaves workspace horizontally', + layout_before => 'S[a b] V[c d* T[e f g*]]', + layout_after => 'S[a b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + is(focused_ws, 'right-top', 'Correct workspace focused'); + }); + +############################################################################### +focus_wrapping('no'); +# See issue #2352 +############################################################################### + +cmp_tree( + msg => 'Normal focus up - should work for all options', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a* b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Normal focus right - should work for all options', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b] V[c d T[e f* g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + }); +cmp_tree( + msg => 'Focus leaves workspace vertically', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus down'; + is(focused_ws, 'left-bottom', 'Correct workspace focused'); + }); +cmp_tree( + msg => 'Focus does not wrap vertically', + layout_before => 'S[a* b] V[c d T[e f g]]', + layout_after => 'S[a* b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Focus does not wrap horizontally', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( + msg => 'Directional focus in the orientation of the parent does not wrap', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( + msg => 'Focus leaves workspace horizontally', + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + is(focused_ws, 'right-top', 'Correct workspace focused'); + }); + +############################################################################### +focus_wrapping('force'); +############################################################################### + +cmp_tree( + msg => 'Normal focus up - should work for all options', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a* b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Normal focus right - should work for all options', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b] V[c d T[e f* g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + }); +cmp_tree( + msg => 'Focus does not leave workspace vertically', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a* b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus down'; + is(focused_ws, 'left-top', 'Correct workspace focused'); + }); +cmp_tree( + msg => 'Focus wraps vertically', + layout_before => 'S[a* b] V[c d T[e f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus up'; + }); +cmp_tree( + msg => 'Focus wraps horizontally (focus direction different than parent\'s orientation)', + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g*]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( + msg => 'Directional focus in the orientation of the parent wraps', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b] V[c d T[e f g*]]', + ws => 'left-top', + cb => sub { + cmd 'focus left'; + }); +cmp_tree( # 'focus_wrapping force' exclusive test + msg => 'But leaves when selecting parent', + layout_before => 'S[a b] V[c d T[e* f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus parent, focus right'; + }); +cmp_tree( + msg => 'Focus does not leave workspace horizontally', + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus right'; + is(focused_ws, 'left-top', 'Correct workspace focused'); + }); +cmp_tree( # 'focus_wrapping force|workspace' exclusive test + msg => 'But leaves when selecting parent x2', + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g]]', + ws => 'left-top', + cb => sub { + cmd 'focus parent, focus parent, focus right'; + is(focused_ws, 'right-top', 'Correct workspace focused'); + }); + +exit_gracefully($pid); + + +done_testing; From e5c430e4194910cacc0d50e66b599b7187a94343 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 14 Sep 2018 19:17:47 +0300 Subject: [PATCH 38/76] tree_move: Use direction_t --- include/move.h | 5 ++--- src/move.c | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/move.h b/include/move.h index df644a6b..96a2cef7 100644 --- a/include/move.h +++ b/include/move.h @@ -12,11 +12,10 @@ #include /** - * Moves the given container in the given direction (TOK_LEFT, TOK_RIGHT, - * TOK_UP, TOK_DOWN from cmdparse.l) + * Moves the given container in the given direction * */ -void tree_move(Con *con, int direction); +void tree_move(Con *con, direction_t direction); typedef enum { BEFORE, AFTER } position_t; diff --git a/src/move.c b/src/move.c index e28a91c6..12277fce 100644 --- a/src/move.c +++ b/src/move.c @@ -243,11 +243,10 @@ static void move_to_output_directed(Con *con, direction_t direction) { } /* - * Moves the given container in the given direction (D_LEFT, D_RIGHT, - * D_UP, D_DOWN). + * Moves the given container in the given direction * */ -void tree_move(Con *con, int direction) { +void tree_move(Con *con, direction_t direction) { position_t position; Con *target; From 1e8e4d3e7ff0110346f43b8f32c2eca00959b501 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 14 Sep 2018 20:18:04 +0300 Subject: [PATCH 39/76] Introduce direction / orientation / position conversion functions --- include/data.h | 2 ++ include/move.h | 3 --- include/util.h | 12 ++++++++++++ src/util.c | 20 ++++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/include/data.h b/include/data.h index 8cf38790..b8c31c52 100644 --- a/include/data.h +++ b/include/data.h @@ -59,6 +59,8 @@ typedef enum { D_LEFT, typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; +typedef enum { BEFORE, + AFTER } position_t; typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t; diff --git a/include/move.h b/include/move.h index 96a2cef7..830488b0 100644 --- a/include/move.h +++ b/include/move.h @@ -17,9 +17,6 @@ */ void tree_move(Con *con, direction_t direction); -typedef enum { BEFORE, - AFTER } position_t; - /** * This function detaches 'con' from its parent and inserts it either before or * after 'target'. diff --git a/include/util.h b/include/util.h index 7a2b3083..ad7195ee 100644 --- a/include/util.h +++ b/include/util.h @@ -181,3 +181,15 @@ ssize_t slurp(const char *path, char **buf); * */ orientation_t orientation_from_direction(direction_t direction); + +/** + * Convert a direction to its corresponding position. + * + */ +position_t position_from_direction(direction_t direction); + +/** + * Convert orientation and position to the corresponding direction. + * + */ +direction_t direction_from_orientation_position(orientation_t orientation, position_t position); diff --git a/src/util.c b/src/util.c index 812aad37..8c84a436 100644 --- a/src/util.c +++ b/src/util.c @@ -518,3 +518,23 @@ ssize_t slurp(const char *path, char **buf) { orientation_t orientation_from_direction(direction_t direction) { return (direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT; } + +/* + * Convert a direction to its corresponding position. + * + */ +position_t position_from_direction(direction_t direction) { + return (direction == D_LEFT || direction == D_UP) ? BEFORE : AFTER; +} + +/* + * Convert orientation and position to the corresponding direction. + * + */ +direction_t direction_from_orientation_position(orientation_t orientation, position_t position) { + if (orientation == HORIZ) { + return position == BEFORE ? D_LEFT : D_RIGHT; + } else { + return position == BEFORE ? D_UP : D_DOWN; + } +} From f402f4570244203adf0574524a48a3538d5d9f67 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 14 Sep 2018 19:12:27 +0300 Subject: [PATCH 40/76] Introduce CMD_FOCUS_WARN_CHILDREN --- src/commands.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/commands.c b/src/commands.c index ff1b2720..24a5bd2f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1213,6 +1213,21 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) { ysuccess(true); } +#define CMD_FOCUS_WARN_CHILDREN \ + do { \ + int count = 0; \ + owindow *current; \ + TAILQ_FOREACH(current, &owindows, owindows) { \ + count++; \ + } \ + \ + if (count > 1) { \ + LOG("WARNING: Your criteria for the focus command matches %d containers, " \ + "while only exactly one container can be focused at a time.\n", \ + count); \ + } \ + } while (0) + /* * Implementation of 'focus left|right|up|down'. * @@ -1315,12 +1330,15 @@ void cmd_focus(I3_CMD) { ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n"); yerror("You have to specify which window/container should be focused"); - + return; + } else if (TAILQ_EMPTY(&owindows)) { + yerror("No window matches given criteria"); return; } + CMD_FOCUS_WARN_CHILDREN; + Con *__i3_scratch = workspace_get("__i3_scratch", NULL); - int count = 0; owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { Con *ws = con_get_workspace(current->con); @@ -1332,7 +1350,6 @@ void cmd_focus(I3_CMD) { /* In case this is a scratchpad window, call scratchpad_show(). */ if (ws == __i3_scratch) { scratchpad_show(current->con); - count++; /* While for the normal focus case we can change focus multiple * times and only a single window ends up focused, we could show * multiple scratchpad windows. So, rather break here. */ @@ -1341,16 +1358,10 @@ void cmd_focus(I3_CMD) { LOG("focusing %p / %s\n", current->con, current->con->name); con_activate_unblock(current->con); - count++; } - if (count > 1) - LOG("WARNING: Your criteria for the focus command matches %d containers, " - "while only exactly one container can be focused at a time.\n", - count); - cmd_output->needs_tree_render = true; - ysuccess(count > 0); + ysuccess(true); } /* From bbc4c99c724017c5f2b224f0137715ed43407326 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Fri, 14 Sep 2018 18:34:43 +0300 Subject: [PATCH 41/76] Refactor tree_next - Makes `tree_next` not recursive. - Adds `focus next|prev [sibling]` command. See (1.) and (2.) in https://github.com/i3/i3/issues/2587#issuecomment-378505551 (Issue also requests move command, not implemented here). - Directional focus command now supports command criteria. Wrapping is not implemented inside a floating container. This was also true before the refactor so I am not changing it here. --- docs/userguide | 7 + include/commands.h | 6 + include/tree.h | 11 +- parser-specs/commands.spec | 8 + src/click.c | 18 +-- src/commands.c | 69 +++++++-- src/tree.c | 250 +++++++++++++++--------------- testcases/t/101-focus.t | 16 +- testcases/t/307-focus-next-prev.t | 72 +++++++++ 9 files changed, 295 insertions(+), 162 deletions(-) create mode 100644 testcases/t/307-focus-next-prev.t diff --git a/docs/userguide b/docs/userguide index ea65bcd1..a8b0e00d 100644 --- a/docs/userguide +++ b/docs/userguide @@ -2053,6 +2053,12 @@ parent:: child:: The opposite of +focus parent+, sets the focus to the last focused child container. +next|prev:: + Automatically sets focus to the adjacent container. If +sibling+ is + specified, the command will focus the exact sibling container, + including non-leaf containers like split containers. Otherwise, it is + an automatic version of +focus left|right|up|down+ in the orientation + of the parent container. floating:: Sets focus to the last focused floating container. tiling:: @@ -2068,6 +2074,7 @@ output:: focus focus left|right|down|up focus parent|child|floating|tiling|mode_toggle +focus next|prev [sibling] focus output left|right|up|down|primary| ---------------------------------------------- diff --git a/include/commands.h b/include/commands.h index 0137460f..27d631a2 100644 --- a/include/commands.h +++ b/include/commands.h @@ -182,6 +182,12 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command); */ void cmd_focus_direction(I3_CMD, const char *direction); +/** + * Implementation of 'focus next|prev sibling' + * + */ +void cmd_focus_sibling(I3_CMD, const char *direction); + /** * Implementation of 'focus tiling|floating|mode_toggle'. * diff --git a/include/tree.h b/include/tree.h index 12170f94..0b758d53 100644 --- a/include/tree.h +++ b/include/tree.h @@ -59,11 +59,16 @@ bool level_down(void); void tree_render(void); /** - * Changes focus in the given way (next/previous) and given orientation - * (horizontal/vertical). + * Changes focus in the given direction * */ -void tree_next(char way, orientation_t orientation); +void tree_next(Con *con, direction_t direction); + +/** + * Get the previous / next sibling + * + */ +Con *get_tree_next_sibling(Con *con, position_t direction); /** * Closes the given container including all children. diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 6b015188..ed9cf0f2 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -146,6 +146,8 @@ state WORKSPACE_NUMBER: state FOCUS: direction = 'left', 'right', 'up', 'down' -> call cmd_focus_direction($direction) + direction = 'prev', 'next' + -> FOCUS_AUTO 'output' -> FOCUS_OUTPUT window_mode = 'tiling', 'floating', 'mode_toggle' @@ -155,6 +157,12 @@ state FOCUS: end -> call cmd_focus() +state FOCUS_AUTO: + 'sibling' + -> call cmd_focus_sibling($direction) + end + -> call cmd_focus_direction($direction) + state FOCUS_OUTPUT: output = string -> call cmd_focus_output($output) diff --git a/src/click.c b/src/click.c index 8ab5c5f0..75710a82 100644 --- a/src/click.c +++ b/src/click.c @@ -212,21 +212,13 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod event->detail == XCB_BUTTON_SCROLL_LEFT || event->detail == XCB_BUTTON_SCROLL_RIGHT)) { DLOG("Scrolling on a window decoration\n"); - orientation_t orientation = con_orientation(con->parent); /* Use the focused child of the tabbed / stacked container, not the * container the user scrolled on. */ - Con *focused = con->parent; - focused = TAILQ_FIRST(&(focused->focus_head)); - con_activate(con_descend_focused(focused)); - /* To prevent scrolling from going outside the container (see ticket - * #557), we first check if scrolling is possible at all. */ - bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); - bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL); - if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) { - tree_next('p', orientation); - } else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) { - tree_next('n', orientation); - } + Con *current = TAILQ_FIRST(&(con->parent->focus_head)); + const position_t direction = + (event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) ? BEFORE : AFTER; + Con *next = get_tree_next_sibling(current, direction); + con_activate(con_descend_focused(next ? next : current)); goto done; } diff --git a/src/commands.c b/src/commands.c index 24a5bd2f..5527860c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1229,23 +1229,62 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) { } while (0) /* - * Implementation of 'focus left|right|up|down'. + * Implementation of 'focus left|right|up|down|next|prev'. * */ -void cmd_focus_direction(I3_CMD, const char *direction) { - switch (parse_direction(direction)) { - case D_LEFT: - tree_next('p', HORIZ); - break; - case D_RIGHT: - tree_next('n', HORIZ); - break; - case D_UP: - tree_next('p', VERT); - break; - case D_DOWN: - tree_next('n', VERT); - break; +void cmd_focus_direction(I3_CMD, const char *direction_str) { + HANDLE_EMPTY_MATCH; + CMD_FOCUS_WARN_CHILDREN; + + direction_t direction; + position_t position; + bool auto_direction = true; + if (strcmp(direction_str, "prev") == 0) { + position = BEFORE; + } else if (strcmp(direction_str, "next") == 0) { + position = AFTER; + } else { + auto_direction = false; + direction = parse_direction(direction_str); + } + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + Con *ws = con_get_workspace(current->con); + if (!ws || con_is_internal(ws)) { + continue; + } + if (auto_direction) { + orientation_t o = con_orientation(current->con->parent); + direction = direction_from_orientation_position(o, position); + } + tree_next(current->con, direction); + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + +/* + * Implementation of 'focus next|prev sibling' + * + */ +void cmd_focus_sibling(I3_CMD, const char *direction_str) { + HANDLE_EMPTY_MATCH; + CMD_FOCUS_WARN_CHILDREN; + + const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER; + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + Con *ws = con_get_workspace(current->con); + if (!ws || con_is_internal(ws)) { + continue; + } + Con *next = get_tree_next_sibling(current->con, direction); + if (next) { + con_activate(next); + } } cmd_output->needs_tree_render = true; diff --git a/src/tree.c b/src/tree.c index 4057d177..039c3a5e 100644 --- a/src/tree.c +++ b/src/tree.c @@ -462,170 +462,162 @@ void tree_render(void) { DLOG("-- END RENDERING --\n"); } +static Con *get_tree_next_workspace(Con *con, direction_t direction) { + if (con_get_fullscreen_con(con, CF_GLOBAL)) { + DLOG("Cannot change workspace while in global fullscreen mode.\n"); + return NULL; + } + + Output *current_output = get_output_containing(con->rect.x, con->rect.y); + if (!current_output) { + return NULL; + } + DLOG("Current output is %s\n", output_primary_name(current_output)); + + Output *next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT); + if (!next_output) { + return NULL; + } + DLOG("Next output is %s\n", output_primary_name(next_output)); + + /* Find visible workspace on next output */ + Con *workspace = NULL; + GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child)); + return workspace; +} + /* - * Recursive function to walk the tree until a con can be found to focus. + * Returns the next / previous container to focus in the given direction. Does + * not modify focus and ensures focus restrictions for fullscreen containers + * are respected. * */ -static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) { - /* When dealing with fullscreen containers, it's necessary to go up to the - * workspace level, because 'focus $dir' will start at the con's real - * position in the tree, and it may not be possible to get to the edge - * normally due to fullscreen focusing restrictions. */ - if (con->fullscreen_mode == CF_OUTPUT && con->type != CT_WORKSPACE) - con = con_get_workspace(con); +static Con *get_tree_next(Con *con, direction_t direction) { + const bool previous = position_from_direction(direction) == BEFORE; + const orientation_t orientation = orientation_from_direction(direction); - /* Stop recursing at workspaces after attempting to switch to next - * workspace if possible. */ - if (con->type == CT_WORKSPACE) { - if (con_get_fullscreen_con(con, CF_GLOBAL)) { - DLOG("Cannot change workspace while in global fullscreen mode.\n"); - return false; + Con *first_wrap = NULL; + while (con->type != CT_WORKSPACE) { + if (con->fullscreen_mode == CF_OUTPUT) { + /* We've reached a fullscreen container. Directional focus should + * now operate on the workspace level. */ + con = con_get_workspace(con); + break; + } else if (con->fullscreen_mode == CF_GLOBAL) { + /* Focus changes should happen only inside the children of a global + * fullscreen container. */ + return first_wrap; } - Output *current_output = get_output_containing(con->rect.x, con->rect.y); - Output *next_output; - if (!current_output) - return false; - DLOG("Current output is %s\n", output_primary_name(current_output)); + Con *const parent = con->parent; + if (con->type == CT_FLOATING_CON) { + if (orientation != HORIZ) { + /* up/down does not change floating containers */ + return NULL; + } - /* Try to find next output */ - direction_t direction; - if (way == 'n' && orientation == HORIZ) - direction = D_RIGHT; - else if (way == 'p' && orientation == HORIZ) - direction = D_LEFT; - else if (way == 'n' && orientation == VERT) - direction = D_DOWN; - else if (way == 'p' && orientation == VERT) - direction = D_UP; - else - return false; + /* left/right focuses the previous/next floating container */ + Con *next = previous ? TAILQ_PREV(con, floating_head, floating_windows) + : TAILQ_NEXT(con, floating_windows); + /* If there is no next/previous container, wrap */ + if (!next) { + next = previous ? TAILQ_LAST(&(parent->floating_head), floating_head) + : TAILQ_FIRST(&(parent->floating_head)); + } + /* Our parent does not list us in floating heads? */ + assert(next); - next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT); - if (!next_output) - return false; - DLOG("Next output is %s\n", output_primary_name(next_output)); + return next; + } - /* Find visible workspace on next output */ - Con *workspace = NULL; - GREP_FIRST(workspace, output_get_content(next_output->con), workspace_is_visible(child)); + if (con_num_children(parent) > 1 && con_orientation(parent) == orientation) { + Con *const next = previous ? TAILQ_PREV(con, nodes_head, nodes) + : TAILQ_NEXT(con, nodes); + if (next && con_fullscreen_permits_focusing(next)) { + return next; + } + Con *const wrap = previous ? TAILQ_LAST(&(parent->nodes_head), nodes_head) + : TAILQ_FIRST(&(parent->nodes_head)); + switch (config.focus_wrapping) { + case FOCUS_WRAPPING_OFF: + break; + case FOCUS_WRAPPING_ON: + if (!first_wrap && con_fullscreen_permits_focusing(wrap)) { + first_wrap = wrap; + } + break; + case FOCUS_WRAPPING_FORCE: + /* 'force' should always return to ensure focus doesn't + * leave the parent. */ + if (next) { + return NULL; /* blocked by fullscreen */ + } + return con_fullscreen_permits_focusing(wrap) ? wrap : NULL; + } + } + + con = parent; + } + + assert(con->type == CT_WORKSPACE); + Con *workspace = get_tree_next_workspace(con, direction); + return workspace ? workspace : first_wrap; +} + +/* + * Changes focus in the given direction + * + */ +void tree_next(Con *con, direction_t direction) { + Con *next = get_tree_next(con, direction); + if (!next) { + return; + } + if (next->type == CT_WORKSPACE) { /* Show next workspace and focus appropriate container if possible. */ - if (!workspace) - return false; - /* Use descend_focused first to give higher priority to floating or * tiling fullscreen containers. */ - Con *focus = con_descend_focused(workspace); + Con *focus = con_descend_focused(next); if (focus->fullscreen_mode == CF_NONE) { - Con *focus_tiling = con_descend_tiling_focused(workspace); + Con *focus_tiling = con_descend_tiling_focused(next); /* If descend_tiling returned a workspace then focus is either a * floating container or the same workspace. */ - if (focus_tiling != workspace) { + if (focus_tiling != next) { focus = focus_tiling; } } - workspace_show(workspace); + workspace_show(next); con_activate(focus); x_set_warp_to(&(focus->rect)); - return true; - } - - Con *parent = con->parent; - - if (con->type == CT_FLOATING_CON) { - if (orientation != HORIZ) - return false; - - /* left/right focuses the previous/next floating container */ - Con *next; - if (way == 'n') - next = TAILQ_NEXT(con, floating_windows); - else - next = TAILQ_PREV(con, floating_head, floating_windows); - - /* If there is no next/previous container, wrap */ - if (!next) { - if (way == 'n') - next = TAILQ_FIRST(&(parent->floating_head)); - else - next = TAILQ_LAST(&(parent->floating_head), floating_head); - } - - /* Still no next/previous container? bail out */ - if (!next) - return false; - - /* Raise the floating window on top of other windows preserving - * relative stack order */ + return; + } else if (next->type == CT_FLOATING_CON) { + /* Raise the floating window on top of other windows preserving relative + * stack order */ + Con *parent = next->parent; while (TAILQ_LAST(&(parent->floating_head), floating_head) != next) { Con *last = TAILQ_LAST(&(parent->floating_head), floating_head); TAILQ_REMOVE(&(parent->floating_head), last, floating_windows); TAILQ_INSERT_HEAD(&(parent->floating_head), last, floating_windows); } - - con_activate(con_descend_focused(next)); - return true; } - /* If the orientation does not match or there is no other con to focus, we - * need to go higher in the hierarchy */ - if (con_orientation(parent) != orientation || - con_num_children(parent) == 1) - return _tree_next(parent, way, orientation, wrap); - - Con *current = TAILQ_FIRST(&(parent->focus_head)); - /* TODO: when can the following happen (except for floating windows, which - * are handled above)? */ - if (TAILQ_EMPTY(&(parent->nodes_head))) { - DLOG("nothing to focus\n"); - return false; - } - - Con *next; - if (way == 'n') - next = TAILQ_NEXT(current, nodes); - else - next = TAILQ_PREV(current, nodes_head, nodes); - - if (!next) { - if (config.focus_wrapping != FOCUS_WRAPPING_FORCE) { - /* If there is no next/previous container, we check if we can focus one - * when going higher (without wrapping, though). If so, we are done, if - * not, we wrap */ - if (_tree_next(parent, way, orientation, false)) - return true; - - if (!wrap) - return false; - } - - if (way == 'n') - next = TAILQ_FIRST(&(parent->nodes_head)); - else - next = TAILQ_LAST(&(parent->nodes_head), nodes_head); - } - - /* Don't violate fullscreen focus restrictions. */ - if (!con_fullscreen_permits_focusing(next)) - return false; - - /* 3: focus choice comes in here. at the moment we will go down - * until we find a window */ - /* TODO: check for window, atm we only go down as far as possible */ + workspace_show(con_get_workspace(next)); con_activate(con_descend_focused(next)); - return true; } /* - * Changes focus in the given way (next/previous) and given orientation - * (horizontal/vertical). + * Get the previous / next sibling * */ -void tree_next(char way, orientation_t orientation) { - _tree_next(focused, way, orientation, - config.focus_wrapping != FOCUS_WRAPPING_OFF); +Con *get_tree_next_sibling(Con *con, position_t direction) { + Con *to_focus = (direction == BEFORE ? TAILQ_PREV(con, nodes_head, nodes) + : TAILQ_NEXT(con, nodes)); + if (to_focus && con_fullscreen_permits_focusing(to_focus)) { + return to_focus; + } + return NULL; } /* diff --git a/testcases/t/101-focus.t b/testcases/t/101-focus.t index 9d42345d..1e87a544 100644 --- a/testcases/t/101-focus.t +++ b/testcases/t/101-focus.t @@ -35,9 +35,13 @@ my $bottom = open_window; # end sleeping for half a second to make sure i3 reacted # sub focus_after { - my $msg = shift; + my ($msg, $win_id) = @_; - cmd $msg; + if (defined($win_id)) { + cmd "[id=$win_id] $msg"; + } else { + cmd $msg; + } return $x->input_focus; } @@ -50,6 +54,14 @@ is($focus, $mid->id, "Middle window focused"); $focus = focus_after('focus up'); is($focus, $top->id, "Top window focused"); +# Same using command criteria +$focus = focus_after('focus up', $bottom->id); +is($focus, $mid->id, "Middle window focused"); + +cmd 'focus down'; +$focus = focus_after('focus up', $mid->id); +is($focus, $top->id, "Top window focused"); + ##################################################################### # Test focus wrapping ##################################################################### diff --git a/testcases/t/307-focus-next-prev.t b/testcases/t/307-focus-next-prev.t new file mode 100644 index 00000000..c7f06589 --- /dev/null +++ b/testcases/t/307-focus-next-prev.t @@ -0,0 +1,72 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test focus next|prev +# Ticket: #2587 +use i3test; + +cmp_tree( + msg => "cmd 'prev' selects leaf 1/2", + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b] V[c* d T[e f g]]', + cb => sub { + cmd 'focus prev'; + }); + +cmp_tree( + msg => "cmd 'prev' selects leaf 2/2", + layout_before => 'S[a b] V[c* d T[e f g]]', + layout_after => 'S[a b*] V[c d T[e f g]]', + cb => sub { + # c* -> V -> b* + cmd 'focus parent, focus prev'; + }); + +cmp_tree( + msg => "cmd 'prev sibling' selects leaf again", + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b] V[c* d T[e f g]]', + cb => sub { + cmd 'focus prev sibling'; + }); + +cmp_tree( + msg => "cmd 'next' selects leaf", + # Notice that g is the last to open before focus moves to d* + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b] V[c d T[e f g*]]', + cb => sub { + cmd 'focus next'; + }); + +cmp_tree( + msg => "cmd 'next sibling' selects parent 1/2", + layout_before => 'S[a b] V[c d* T[e f g]]', + layout_after => 'S[a b] V[c d T*[e f g]]', + cb => sub { + cmd 'focus next sibling'; + }); + +cmp_tree( + msg => "cmd 'next sibling' selects parent 2/2", + layout_before => 'S[a b*] V[c d T[e f g]]', + layout_after => 'S[a b] V*[c d T[e f g]]', + cb => sub { + # b* -> S* -> V* + cmd 'focus parent, focus next sibling'; + }); + +done_testing; From ee30c34b5c2a16140dcbc7ddc67b5a421faefe9b Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 14 Oct 2019 03:54:05 +0300 Subject: [PATCH 42/76] Fix small leak in i3bar's main If -s is used, the socket_path returned by getenv is never freed. Also some small rearrangements. --- i3bar/src/main.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/i3bar/src/main.c b/i3bar/src/main.c index 26ea0eeb..aa6bdbea 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -92,13 +92,7 @@ static void sig_cb(struct ev_loop *loop, ev_signal *watcher, int revents) { } int main(int argc, char **argv) { - int opt; - int option_index = 0; - char *socket_path = getenv("I3SOCK"); - if (socket_path != NULL) { - socket_path = sstrdup(socket_path); - } - char *i3_default_sock_path = "/tmp/i3-ipc.sock"; + char *socket_path = NULL; /* Initialize the standard config to use 0 as default */ memset(&config, '\0', sizeof(config_t)); @@ -112,6 +106,8 @@ int main(int argc, char **argv) { {"verbose", no_argument, 0, 'V'}, {NULL, 0, 0, 0}}; + int opt; + int option_index = 0; while ((opt = getopt_long(argc, argv, "b:s:thvV", long_opt, &option_index)) != -1) { switch (opt) { case 's': @@ -144,10 +140,17 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - main_loop = ev_default_loop(0); - + main_loop = ev_default_loop(0); /* needed in init_xcb_early */ char *atom_sock_path = init_xcb_early(); + /* Select a socket_path if the user hasn't specified one */ + if (socket_path == NULL) { + socket_path = getenv("I3SOCK"); + if (socket_path != NULL) { + socket_path = sstrdup(socket_path); + } + } + if (socket_path == NULL) { socket_path = atom_sock_path; } else { @@ -155,8 +158,9 @@ int main(int argc, char **argv) { } if (socket_path == NULL) { + char *i3_default_sock_path = "/tmp/i3-ipc.sock"; ELOG("No socket path specified, default to %s\n", i3_default_sock_path); - socket_path = expand_path(i3_default_sock_path); + socket_path = sstrdup(i3_default_sock_path); } init_dpi(); From b1723f0549c38eaa7fcb2a62ec3d4d4749531657 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 14 Oct 2019 12:24:30 +0300 Subject: [PATCH 43/76] i3-nagbar: Don't use DLOG debuglog() is empty and no option to enable it exists --- i3-nagbar/main.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index ec3e25fb..49c9d931 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -87,7 +87,7 @@ void verboselog(char *fmt, ...) { va_list args; va_start(args, fmt); - vfprintf(stdout, fmt, args); + vfprintf(stderr, fmt, args); va_end(args); } @@ -282,11 +282,12 @@ static xcb_rectangle_t get_window_position(void) { xcb_randr_get_screen_resources_current_reply_t *res = NULL; if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) { - DLOG("Could not determine the primary output.\n"); + LOG("Could not determine the primary output.\n"); goto free_resources; } if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) { + LOG("Could not query screen resources.\n"); goto free_resources; } @@ -304,10 +305,10 @@ static xcb_rectangle_t get_window_position(void) { if (crtc == NULL) goto free_resources; - DLOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n", - crtc->x, crtc->y, crtc->width, crtc->height); + LOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n", + crtc->x, crtc->y, crtc->width, crtc->height); if (crtc->width == 0 || crtc->height == 0) { - DLOG("Primary output is not active, ignoring it.\n"); + LOG("Primary output is not active, ignoring it.\n"); goto free_resources; } From a3f94783e5956c9ca5da5af09697fe51e75deb05 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 14 Oct 2019 12:25:33 +0300 Subject: [PATCH 44/76] i3-nagbar: get_window_position: Improve logging --- i3-nagbar/main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 49c9d931..e2c6e516 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -295,15 +295,19 @@ static xcb_rectangle_t get_window_position(void) { xcb_randr_get_output_info_reply(conn, xcb_randr_get_output_info(conn, primary->output, res->config_timestamp), NULL); - if (output == NULL || output->crtc == XCB_NONE) + if (output == NULL || output->crtc == XCB_NONE) { + LOG("Could not query primary screen.\n"); goto free_resources; + } xcb_randr_get_crtc_info_reply_t *crtc = xcb_randr_get_crtc_info_reply(conn, xcb_randr_get_crtc_info(conn, output->crtc, res->config_timestamp), NULL); - if (crtc == NULL) + if (crtc == NULL) { + LOG("Could not get CRTC.\n"); goto free_resources; + } LOG("Found primary output on position x = %i / y = %i / w = %i / h = %i.\n", crtc->x, crtc->y, crtc->width, crtc->height); From ffde51e50f4472af4969688bc582e67bcde21618 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Mon, 14 Oct 2019 12:26:10 +0300 Subject: [PATCH 45/76] i3-nagbar: Fix small leak --- i3-nagbar/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index e2c6e516..c7b23da5 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -384,10 +384,11 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { case 'v': + free(pattern); printf("i3-nagbar " I3_VERSION "\n"); return 0; case 'f': - FREE(pattern); + free(pattern); pattern = sstrdup(optarg); break; case 'm': @@ -398,6 +399,7 @@ int main(int argc, char *argv[]) { bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR); break; case 'h': + free(pattern); printf("i3-nagbar " I3_VERSION "\n"); printf("i3-nagbar [-m ] [-b