From 6562f440c5a630c1551b36c29a08c74c2b897182 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 7 Aug 2013 21:21:47 +0200 Subject: [PATCH 01/67] update debian/ packaging --- debian/changelog | 20 +++++++++++++++++--- debian/control | 2 +- debian/rules | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/debian/changelog b/debian/changelog index 80a20e3c..f3855363 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,22 @@ -i3-wm (4.5.2-1) experimental; urgency=low +i3-wm (4.6.1-1) unstable; urgency=low - * NOT YET RELEASED + * NOT YET RELEASED. - -- Michael Stapelberg Mon, 18 Mar 2013 23:01:30 +0100 + -- Michael Stapelberg Wed, 07 Aug 2013 20:53:26 +0200 + +i3-wm (4.6-1) unstable; urgency=low + + * New upstream release. + + -- Michael Stapelberg Wed, 07 Aug 2013 20:53:26 +0200 + +i3-wm (4.5.1-2) unstable; urgency=low + + * experimental to unstable because i3-wm 4.5.1 was only in experimental due + to the freeze. + * bump standards-version to 3.9.4 (no changes necessary) + + -- Michael Stapelberg Tue, 14 May 2013 20:48:16 +0200 i3-wm (4.5.1-1) experimental; urgency=low diff --git a/debian/control b/debian/control index f9ecb558..ede86509 100644 --- a/debian/control +++ b/debian/control @@ -21,7 +21,7 @@ Build-Depends: debhelper (>= 7.0.50~), libcairo2-dev, libpango1.0-dev, libpod-simple-perl -Standards-Version: 3.9.3 +Standards-Version: 3.9.4 Homepage: http://i3wm.org/ Package: i3 diff --git a/debian/rules b/debian/rules index 3ae79ddd..55c72b51 100755 --- a/debian/rules +++ b/debian/rules @@ -38,7 +38,7 @@ override_dh_auto_build: $(MAKE) -C docs override_dh_installchangelogs: - dh_installchangelogs RELEASE-NOTES-4.5.1 + dh_installchangelogs RELEASE-NOTES-4.6 override_dh_install: $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install From 3be0b519c9a68ef7403d6dcffb1ccc381117ac2c Mon Sep 17 00:00:00 2001 From: syl20bnr Date: Sat, 3 Aug 2013 21:29:07 -0400 Subject: [PATCH 02/67] Update documentation paragraph on variables see http://infra.in.zekjur.net/archives/i3-discuss/2013-August/001377.html --- docs/userguide | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/userguide b/docs/userguide index f2645998..f2f51173 100644 --- a/docs/userguide +++ b/docs/userguide @@ -570,11 +570,12 @@ set $m Mod1 bindsym $m+Shift+r restart ------------------------ -Variables are directly replaced in the file when parsing. There is no fancy -handling and there are absolutely no plans to change this. If you need a more -dynamic configuration you should create a little script which generates a -configuration file and run it before starting i3 (for example in your -+~/.xsession+ file). +Variables are directly replaced in the file when parsing. Variables expansion +is not recursive so it is not possible to define a variable with a value +containing another variable. There is no fancy handling and there are +absolutely no plans to change this. If you need a more dynamic configuration +you should create a little script which generates a configuration file and run +it before starting i3 (for example in your +~/.xsession+ file). === Automatically putting clients on specific workspaces From da20cd397c5079d1950211610134ced41034d2af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Jul 2013 10:58:06 +0200 Subject: [PATCH 03/67] switch from libXcursor to xcb-util-cursor --- DEPENDS | 3 +++ common.mk | 4 ++-- debian/control | 2 +- include/xcursor.h | 6 ++--- src/floating.c | 2 +- src/xcursor.c | 57 ++++++++++++++++++++++------------------------- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/DEPENDS b/DEPENDS index 4b090272..94bfd266 100644 --- a/DEPENDS +++ b/DEPENDS @@ -10,6 +10,7 @@ │ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │ │ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │ │ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ +│ util-cursor³│ 0.0.99 │ 0.0.99 │ http://xcb.freedesktop.org/dist/ │ │ libev │ 4.0 │ 4.11 │ http://libev.schmorp.de/ │ │ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │ │ asciidoc │ 8.3.0 │ 8.6.4 │ http://www.methods.co.nz/asciidoc/ │ @@ -26,6 +27,8 @@ ¹ libsn = libstartup-notification ² Pod::Simple is a Perl module required for converting the testsuite documentation to HTML. See http://michael.stapelberg.de/cpan/#Pod::Simple + ³ xcb-util-cursor, to be precise. Might be considered part of xcb-util, or not + :-). i3bar, i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new dependencies. diff --git a/common.mk b/common.mk index de5c7e98..0214abfa 100644 --- a/common.mk +++ b/common.mk @@ -111,8 +111,8 @@ X11_CFLAGS := $(call cflags_for_lib, x11) X11_LIBS := $(call ldflags_for_lib, x11,X11) # Xcursor -XCURSOR_CFLAGS := $(call cflags_for_lib, xcursor) -XCURSOR_LIBS := $(call ldflags_for_lib, xcursor,Xcursor) +XCURSOR_CFLAGS := $(call cflags_for_lib, xcb-cursor) +XCURSOR_LIBS := $(call ldflags_for_lib, xcb-cursor,xcb-cursor) # yajl YAJL_CFLAGS := $(call cflags_for_lib, yajl) diff --git a/debian/control b/debian/control index ede86509..558b0127 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,7 @@ Build-Depends: debhelper (>= 7.0.50~), libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, - libxcursor-dev, + libxcb-cursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, diff --git a/include/xcursor.h b/include/xcursor.h index bfe37c39..868fee78 100644 --- a/include/xcursor.h +++ b/include/xcursor.h @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE) * * xcursor.c: libXcursor support for themed cursors. * @@ -10,7 +10,7 @@ #ifndef I3_XCURSOR_CURSOR_H #define I3_XCURSOR_CURSOR_H -#include +#include enum xcursor_cursor_t { XCURSOR_CURSOR_POINTER = 0, @@ -26,7 +26,7 @@ enum xcursor_cursor_t { }; void xcursor_load_cursors(void); -Cursor xcursor_get_cursor(enum xcursor_cursor_t c); +xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c); int xcursor_get_xcb_cursor(enum xcursor_cursor_t c); /** diff --git a/src/floating.c b/src/floating.c index 643f204b..97b7d884 100644 --- a/src/floating.c +++ b/src/floating.c @@ -569,7 +569,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t if (con != NULL) memcpy(&old_rect, &(con->rect), sizeof(Rect)); - Cursor xcursor = (cursor && xcursor_supported) ? + xcb_cursor_t xcursor = (cursor && xcursor_supported) ? xcursor_get_cursor(cursor) : XCB_NONE; /* Grab the pointer */ diff --git a/src/xcursor.c b/src/xcursor.c index 90fd69dd..dcbe2ad0 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -4,20 +4,20 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE) * - * xcursor.c: libXcursor support for themed cursors. + * xcursor.c: xcursor support for themed cursors. * */ #include -#include -#include +#include #include "i3.h" #include "xcb.h" #include "xcursor.h" -static Cursor cursors[XCURSOR_CURSOR_MAX]; +static xcb_cursor_context_t *ctx; +static xcb_cursor_t cursors[XCURSOR_CURSOR_MAX]; static const int xcb_cursors[XCURSOR_CURSOR_MAX] = { XCB_CURSOR_LEFT_PTR, @@ -26,23 +26,26 @@ static const int xcb_cursors[XCURSOR_CURSOR_MAX] = { XCB_CURSOR_WATCH }; -static Cursor load_cursor(const char *name) { - Cursor c = XcursorLibraryLoadCursor(xlibdpy, name); - if (c == None) - xcursor_supported = false; - return c; -} - void xcursor_load_cursors(void) { - cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr"); - cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow"); - cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow"); - cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch"); - cursors[XCURSOR_CURSOR_MOVE] = load_cursor("fleur"); - cursors[XCURSOR_CURSOR_TOP_LEFT_CORNER] = load_cursor("top_left_corner"); - cursors[XCURSOR_CURSOR_TOP_RIGHT_CORNER] = load_cursor("top_right_corner"); - cursors[XCURSOR_CURSOR_BOTTOM_LEFT_CORNER] = load_cursor("bottom_left_corner"); - cursors[XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER] = load_cursor("bottom_right_corner"); + if (xcb_cursor_context_new(conn, root_screen, &ctx) < 0) { + ELOG("xcursor support unavailable\n"); + xcursor_supported = false; + return; + } +#define LOAD_CURSOR(constant, name) \ + do { \ + cursors[constant] = xcb_cursor_load_cursor(ctx, name); \ + } while (0) + LOAD_CURSOR(XCURSOR_CURSOR_POINTER, "left_ptr"); + LOAD_CURSOR(XCURSOR_CURSOR_RESIZE_HORIZONTAL, "sb_h_double_arrow"); + LOAD_CURSOR(XCURSOR_CURSOR_RESIZE_VERTICAL, "sb_v_double_arrow"); + LOAD_CURSOR(XCURSOR_CURSOR_WATCH, "watch"); + LOAD_CURSOR(XCURSOR_CURSOR_MOVE, "fleur"); + LOAD_CURSOR(XCURSOR_CURSOR_TOP_LEFT_CORNER, "top_left_corner"); + LOAD_CURSOR(XCURSOR_CURSOR_TOP_RIGHT_CORNER, "top_right_corner"); + LOAD_CURSOR(XCURSOR_CURSOR_BOTTOM_LEFT_CORNER, "bottom_left_corner"); + LOAD_CURSOR(XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER, "bottom_right_corner"); +#undef LOAD_CURSOR } /* @@ -51,19 +54,13 @@ void xcursor_load_cursors(void) { * This function is called when i3 is initialized, because with some login * managers, the root window will not have a cursor otherwise. * - * We have a separate xcursor function to use the same X11 connection as the - * xcursor_load_cursors() function. If we mix the Xlib and the XCB connection, - * races might occur (even though we flush the Xlib connection). - * */ void xcursor_set_root_cursor(int cursor_id) { - XSetWindowAttributes attributes; - attributes.cursor = xcursor_get_cursor(cursor_id); - XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes); - XFlush(xlibdpy); + xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, + (uint32_t[]){ xcursor_get_cursor(cursor_id) }); } -Cursor xcursor_get_cursor(enum xcursor_cursor_t c) { +xcb_cursor_t xcursor_get_cursor(enum xcursor_cursor_t c) { assert(c >= 0 && c < XCURSOR_CURSOR_MAX); return cursors[c]; } From 431e98dc352437f1118ea5be9d5c55d2354db9f5 Mon Sep 17 00:00:00 2001 From: Sebastian Ullrich Date: Wed, 7 Aug 2013 17:20:22 +0200 Subject: [PATCH 04/67] Respect workspace numbers when looking for a free workspace name This prevents a ws '1' appearing on a new output when there's already a ws '1: www' on an existing output --- src/workspace.c | 9 +++---- testcases/t/515-create-workspace.t | 40 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 testcases/t/515-create-workspace.t diff --git a/src/workspace.c b/src/workspace.c index af9325f7..3f70ced7 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -197,17 +197,16 @@ Con *create_workspace_on_output(Output *output, Con *content) { while (exists) { c++; - FREE(ws->name); - sasprintf(&(ws->name), "%d", c); + ws->num = c; current = NULL; TAILQ_FOREACH(out, &(croot->nodes_head), nodes) - GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name)); + GREP_FIRST(current, output_get_content(out), child->num == ws->num); exists = (current != NULL); - DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists); + DLOG("result for ws %d: exists = %d\n", c, exists); } - ws->num = c; + sasprintf(&(ws->name), "%d", c); } con_attach(ws, content, false); diff --git a/testcases/t/515-create-workspace.t b/testcases/t/515-create-workspace.t new file mode 100644 index 00000000..be790bf0 --- /dev/null +++ b/testcases/t/515-create-workspace.t @@ -0,0 +1,40 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests that new workspace names are taken from the config, +# then from the first free number starting with 1. +# +use i3test i3_autostart => 0; + +my $config = <get_workspaces->recv; + +is($ws->[0]->{name}, '1: eggs', 'new workspace uses config name'); +is($ws->[1]->{name}, '2', 'naming continues with next free number'); + +exit_gracefully($pid); + +done_testing; From a5ee699d36ba350795e6e55ed3e2451b7f2dd338 Mon Sep 17 00:00:00 2001 From: Bas Pape Date: Sun, 18 Aug 2013 18:12:19 +0200 Subject: [PATCH 05/67] Initialize variables to prevent (false) warnings. --- src/commands.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index cde1cd7c..9686dfe4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1950,7 +1950,7 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { * */ bool cmd_bar_mode(char *bar_mode, char *bar_id) { - int mode; + int mode = M_DOCK; bool toggle = false; if (strcmp(bar_mode, "dock") == 0) mode = M_DOCK; @@ -1995,7 +1995,7 @@ bool cmd_bar_mode(char *bar_mode, char *bar_id) { * */ bool cmd_bar_hidden_state(char *bar_hidden_state, char *bar_id) { - int hidden_state; + int hidden_state = S_SHOW; bool toggle = false; if (strcmp(bar_hidden_state, "hide") == 0) hidden_state = S_HIDE; From c9611b320b1ca822b5c0b23cb0c62005a7244f48 Mon Sep 17 00:00:00 2001 From: Leo Gaspard Date: Tue, 20 Aug 2013 02:07:31 +0200 Subject: [PATCH 06/67] Remove debug message when debugging is disabled More precisely, switch from printf to DLOG a "matching: * / *" message, like all the other "matching: * / *" messages. --- src/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 9686dfe4..f4274246 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1521,7 +1521,7 @@ void cmd_fullscreen(I3_CMD, char *fullscreen_mode) { HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { - printf("matching: %p / %s\n", current->con, current->con->name); + DLOG("matching: %p / %s\n", current->con, current->con->name); con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT)); } From 28fafcb1c278f30c215328e044ab60cbfddb1327 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 22 Aug 2013 18:44:21 +0200 Subject: [PATCH 07/67] Remove forgotten libxcursor from DEPENDS (Thanks badboy) --- DEPENDS | 1 - 1 file changed, 1 deletion(-) diff --git a/DEPENDS b/DEPENDS index 94bfd266..083372c7 100644 --- a/DEPENDS +++ b/DEPENDS @@ -17,7 +17,6 @@ │ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │ │ Pod::Simple²│ 3.22 │ 3.22 │ http://search.cpan.org/~dwheeler/Pod-Simple-3.23/ │ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │ -│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │ │ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │ │ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │ │ libsn¹ │ 0.10 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification From a7c005848a8f96f3221dad54178dadd46b4a34dc Mon Sep 17 00:00:00 2001 From: Deiz Date: Thu, 29 Aug 2013 18:15:20 -0400 Subject: [PATCH 08/67] Create pixmaps using the real bar height, rather than screen height. --- i3bar/src/xcb.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 15c68a08..36c2e470 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -59,6 +59,9 @@ xcb_connection_t *conn; /* The font we'll use */ static i3Font font; +/* Overall height of the bar (based on font size) */ +int bar_height; + /* These are only relevant for XKB, which we only need for grabbing modifiers */ Display *xkb_dpy; int xkb_event_base; @@ -240,9 +243,9 @@ void unhide_bars(void) { values[0] = walk->rect.x; if (config.position == POS_TOP) values[1] = walk->rect.y; - else values[1] = walk->rect.y + walk->rect.h - font.height - 6; + else values[1] = walk->rect.y + walk->rect.h - bar_height; values[2] = walk->rect.w; - values[3] = font.height + 6; + values[3] = bar_height; values[4] = XCB_STACK_MODE_ABOVE; DLOG("Reconfiguring Window for output %s to %d,%d\n", walk->name, values[0], values[1]); cookie = xcb_configure_window_checked(xcb_connection, @@ -1061,6 +1064,7 @@ void init_xcb_late(char *fontname) { font = load_font(fontname, true); set_font(&font); DLOG("Calculated Font-height: %d\n", font.height); + bar_height = font.height + 6; xcb_flush(xcb_connection); @@ -1334,7 +1338,7 @@ void realloc_sl_buffer(void) { statusline_pm, xcb_root, MAX(root_screen->width_in_pixels, statusline_width), - root_screen->height_in_pixels); + bar_height); uint32_t mask = XCB_GC_FOREGROUND; uint32_t vals[2] = { colors.bar_bg, colors.bar_bg }; @@ -1407,8 +1411,8 @@ void reconfig_windows(bool redraw_bars) { root_screen->root_depth, walk->bar, xcb_root, - walk->rect.x, walk->rect.y + walk->rect.h - font.height - 6, - walk->rect.w, font.height + 6, + walk->rect.x, walk->rect.y + walk->rect.h - bar_height, + walk->rect.w, bar_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, root_screen->root_visual, @@ -1421,7 +1425,7 @@ void reconfig_windows(bool redraw_bars) { walk->buffer, walk->bar, walk->rect.w, - walk->rect.h); + bar_height); /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */ xcb_void_cookie_t class_cookie; @@ -1482,12 +1486,12 @@ void reconfig_windows(bool redraw_bars) { case POS_NONE: break; case POS_TOP: - strut_partial.top = font.height + 6; + strut_partial.top = bar_height; strut_partial.top_start_x = walk->rect.x; strut_partial.top_end_x = walk->rect.x + walk->rect.w; break; case POS_BOT: - strut_partial.bottom = font.height + 6; + strut_partial.bottom = bar_height; strut_partial.bottom_start_x = walk->rect.x; strut_partial.bottom_end_x = walk->rect.x + walk->rect.w; break; @@ -1541,9 +1545,9 @@ void reconfig_windows(bool redraw_bars) { XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE; values[0] = walk->rect.x; - values[1] = walk->rect.y + walk->rect.h - font.height - 6; + values[1] = walk->rect.y + walk->rect.h - bar_height; values[2] = walk->rect.w; - values[3] = font.height + 6; + values[3] = bar_height; values[4] = XCB_STACK_MODE_ABOVE; DLOG("Destroying buffer for output %s\n", walk->name); @@ -1569,7 +1573,7 @@ void reconfig_windows(bool redraw_bars) { walk->buffer, walk->bar, walk->rect.w, - walk->rect.h); + bar_height); xcb_void_cookie_t map_cookie, umap_cookie; if (redraw_bars) { @@ -1631,7 +1635,7 @@ void draw_bars(bool unhide) { outputs_walk->bargc, XCB_GC_FOREGROUND, &color); - xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, font.height + 6 }; + xcb_rectangle_t rect = { 0, 0, outputs_walk->rect.w, bar_height }; xcb_poly_fill_rectangle(xcb_connection, outputs_walk->buffer, outputs_walk->bargc, From 4ff01e59cae64a5f3702e6cadfbfa3a3ada3b41f Mon Sep 17 00:00:00 2001 From: James Baumgarten Date: Sun, 8 Sep 2013 19:30:39 -0700 Subject: [PATCH 09/67] Clarify userguide description of urgent_workspace configuration --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 76ac73ac..ae3bda2e 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1211,7 +1211,7 @@ inactive_workspace:: will be the case for most workspaces. urgent_workspace:: Border, background and text color for a workspace button when the workspace - window with the urgency hint set. + contains a window with the urgency hint set. Also applies to +mode+ indicators. *Syntax*: ---------------------------------------- From e9b8307829341a50b6c9a2218c7ddfbe3ffbf458 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sat, 14 Sep 2013 14:57:42 -0400 Subject: [PATCH 10/67] Add scratchpad bindings to the default config. --- i3.config | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/i3.config b/i3.config index ff6c9dbe..de7e1fec 100644 --- a/i3.config +++ b/i3.config @@ -84,6 +84,13 @@ bindsym Mod1+a focus parent # focus the child container #bindsym Mod1+d focus child +# move the currently focused window to the scratchpad +bindsym Mod1+Shift+minus move scratchpad + +# Show the next scratchpad window or hide the focused scratchpad window. +# If there are multiple scratchpad windows, this command cycles through them. +bindsym Mod1+minus scratchpad show + # switch to workspace bindsym Mod1+1 workspace 1 bindsym Mod1+2 workspace 2 From 5d005f403cca2f8c8212811dd15271e03c18a515 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 Sep 2013 00:08:27 +0200 Subject: [PATCH 11/67] man/i3-dump-log: document -f (Thanks TonyC) --- man/i3-dump-log.man | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/man/i3-dump-log.man b/man/i3-dump-log.man index eb8ba2f7..45514407 100644 --- a/man/i3-dump-log.man +++ b/man/i3-dump-log.man @@ -1,7 +1,7 @@ i3-dump-log(1) ============== -Michael Stapelberg -v4.1, December 2011 +Michael Stapelberg +v4.6, September 2013 == NAME @@ -9,7 +9,7 @@ i3-dump-log - dumps the i3 SHM log == SYNOPSIS -i3-dump-log [-s ] +i3-dump-log [-s ] [-f] == DESCRIPTION @@ -19,6 +19,9 @@ figuring out what is going on, without permanently logging to a file. With i3-dump-log, you can dump the SHM log to stdout. +The -f flag works like tail -f, i.e. the process does not terminate after +dumping the log, but prints new lines as they appear. + == EXAMPLE i3-dump-log | gzip -9 > /tmp/i3-log.gz From d6b1f1a1b2acfc700075536fd996b371cfc5d89a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 24 Sep 2013 06:35:51 +0200 Subject: [PATCH 12/67] =?UTF-8?q?i3test:=20get=20rid=20of=20the=20smartmat?= =?UTF-8?q?ch=20operator,=20it=E2=80=99s=20experimental=20since=20perl=205?= =?UTF-8?q?.18?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testcases/lib/i3test.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index 9f3a6ea2..476cda03 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -406,7 +406,7 @@ C which directly switches to an unused workspace. sub get_unused_workspace { my @names = get_workspace_names(); my $tmp; - do { $tmp = tmpnam() } while ($tmp ~~ @names); + do { $tmp = tmpnam() } while ((scalar grep { $_ eq $tmp } @names) > 0); $tmp } @@ -626,7 +626,7 @@ Returns true if C<$workspace> is the name of an existing workspace. =cut sub workspace_exists { my ($name) = @_; - ($name ~~ @{get_workspace_names()}) + (scalar grep { $_ eq $name } @{get_workspace_names()}) > 0; } =head2 focused_ws From 3ba8642efd15456ab882a5f548bd6a28169ca3d0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 24 Sep 2013 06:36:08 +0200 Subject: [PATCH 13/67] tests: fix setting the urgency hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X11::XCB < 0.08 had a bug which caused this code to work even though it shouldn’t. --- testcases/t/113-urgent.t | 5 ++++- testcases/t/200-urgency-timer.t | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 2eb853de..bb913819 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -24,8 +24,11 @@ my $_NET_WM_STATE_TOGGLE = 2; sub set_urgency { my ($win, $urgent_flag, $type) = @_; if ($type == 1) { + # Because X11::XCB does not keep track of clearing the urgency hint + # when receiving focus, we just delete it in all cases and then re-set + # it if appropriate. + $win->delete_hint('urgency'); $win->add_hint('urgency') if ($urgent_flag); - $win->delete_hint('urgency') if (!$urgent_flag); } elsif ($type == 2) { my $msg = pack "CCSLLLLLL", X11::XCB::CLIENT_MESSAGE, # response_type diff --git a/testcases/t/200-urgency-timer.t b/testcases/t/200-urgency-timer.t index 730a950a..0fb8c8be 100644 --- a/testcases/t/200-urgency-timer.t +++ b/testcases/t/200-urgency-timer.t @@ -85,6 +85,7 @@ my $w2 = open_window; is($x->input_focus, $w2->id, 'window 2 focused'); cmd "workspace $tmp2"; +$w->delete_hint('urgency'); $w->add_hint('urgency'); sync_with_i3; From ee04f8bfdaeea0c7c592b3888bc4ecabb90f5d80 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 18 Sep 2013 20:06:48 +0200 Subject: [PATCH 14/67] Fix handling of new windows with WM_STATE_FULLSCREEN If the currently focused window is in fullscreen mode, and a new window is opened with WM_STATE_FULLSCREEN set, the new window now becomes the new fullscreen window and gains focus. --- src/manage.c | 15 +++++++++------ testcases/t/100-fullscreen.t | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/manage.c b/src/manage.c index f5bd76ea..238d991c 100644 --- a/src/manage.c +++ b/src/manage.c @@ -321,11 +321,20 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki x_set_name(nc, name); free(name); + /* handle fullscreen containers */ Con *ws = con_get_workspace(nc); Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL); if (fs == NULL) fs = con_get_fullscreen_con(croot, CF_GLOBAL); + xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL); + if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_FULLSCREEN)) { + fs = NULL; + con_toggle_fullscreen(nc, CF_OUTPUT); + } + + FREE(state_reply); + if (fs == NULL) { DLOG("Not in fullscreen mode, focusing\n"); if (!cwindow->dock) { @@ -429,12 +438,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, values); xcb_flush(conn); - reply = xcb_get_property_reply(conn, state_cookie, NULL); - if (xcb_reply_contains_atom(reply, A__NET_WM_STATE_FULLSCREEN)) - con_toggle_fullscreen(nc, CF_OUTPUT); - - FREE(reply); - /* Put the client inside the save set. Upon termination (whether killed or * normal exit does not matter) of the window manager, these clients will * be correctly reparented to their most closest living ancestor (= diff --git a/testcases/t/100-fullscreen.t b/testcases/t/100-fullscreen.t index cec7000a..54b29c9d 100644 --- a/testcases/t/100-fullscreen.t +++ b/testcases/t/100-fullscreen.t @@ -214,4 +214,25 @@ sync_with_i3; # Verify that $swindow was the one that initially remained fullscreen. is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws'); +################################################################################ +# Verify that opening a window with _NET_WM_STATE_FULLSCREEN unfullscreens any +# existing container on the workspace and fullscreens the newly opened window. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window(); + +cmd "fullscreen"; + +is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws'); +is($x->input_focus, $window->id, 'fullscreen window focused'); + +$swindow = open_window({ + fullscreen => 1 +}); + +is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws'); +is($x->input_focus, $swindow->id, 'fullscreen window focused'); + done_testing; From 153259cb1079640e66861b88a877194535387162 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 24 Sep 2013 06:37:40 +0200 Subject: [PATCH 15/67] tests: also get rid of smartmatch in complete-run.pl --- testcases/complete-run.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 560dd4c3..c6e74369 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -143,7 +143,7 @@ my $timingsjson = StartXDummy::slurp('.last_run_timings.json'); # Run 000-load-deps.t first to bail out early when dependencies are missing. my $loadtest = "t/000-load-deps.t"; -if ($loadtest ~~ @testfiles) { +if ((scalar grep { $_ eq $loadtest } @testfiles) > 0) { @testfiles = ($loadtest, grep { $_ ne $loadtest } @testfiles); } From 97b086efd9833d2a787e54417789b279143e43a6 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 19 Sep 2013 14:34:02 +0200 Subject: [PATCH 16/67] Close all children when closing a workspace fixes #591 --- src/tree.c | 9 +++++---- testcases/t/129-focus-after-close.t | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/tree.c b/src/tree.c index 58af674b..4df9f593 100644 --- a/src/tree.c +++ b/src/tree.c @@ -229,6 +229,11 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool return false; } + if (workspace_is_visible(con)) { + DLOG("A visible workspace cannot be killed.\n"); + return false; + } + if (con->window != NULL) { if (kill_window != DONT_KILL_WINDOW) { x_window_kill(con->window->id, kill_window); @@ -359,10 +364,6 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool */ void tree_close_con(kill_window_t kill_window) { assert(focused != NULL); - if (focused->type == CT_WORKSPACE) { - LOG("Cannot close workspace\n"); - return; - } /* There *should* be no possibility to focus outputs / root container */ assert(focused->type != CT_OUTPUT); diff --git a/testcases/t/129-focus-after-close.t b/testcases/t/129-focus-after-close.t index df226e84..8d9ccbb9 100644 --- a/testcases/t/129-focus-after-close.t +++ b/testcases/t/129-focus-after-close.t @@ -119,6 +119,30 @@ sync_with_i3; is(get_focused($tmp), $middle, 'middle container focused'); +############################################################## +# check if the workspace container can be closed +############################################################## + +$tmp = fresh_workspace; + +my $window = open_window(); + +# one window opened on the current workspace +($nodes, $focus) = get_ws_content($tmp); +is(scalar @$nodes, 1, 'workspace contains one node'); + +# focus the workspace +cmd "focus parent"; +cmd "focus parent"; + +# try to kill the workspace +cmd "kill"; +sync_with_i3; + +# the workspace should now be empty +($nodes, $focus) = get_ws_content($tmp); +is(scalar @$nodes, 0, 'workspace is empty'); + ############################################################## # and now for something completely different: # check if the pointer position is relevant when restoring focus From 7098ef602b6f72e7aae27b0d1ee28074b97bc61d Mon Sep 17 00:00:00 2001 From: syl20bnr Date: Thu, 8 Aug 2013 23:30:14 -0400 Subject: [PATCH 17/67] Add new bar.binding_mode_indicator configuration. i3 current behavior hides the binding mode indicator when workspace buttons are disabled. This patch adds a new configuration for i3bar called 'binding_mode_indicator' which acts like the workspace_buttons. It is now possible to configure i3bar to hide the workspace buttons and keep showing the binding mode indicator. This should make the hide workspace buttons configuration more convenient for those who are heavily using binding modes. Default value for binding_mode_indicator is true. --- docs/ipc | 3 + docs/userguide | 25 ++++++- i3bar/include/config.h | 3 +- i3bar/src/config.c | 6 ++ i3bar/src/xcb.c | 127 ++++++++++++++++---------------- include/config.h | 4 + include/config_directives.h | 1 + parser-specs/config.spec | 33 +++++---- src/config_directives.c | 4 + src/ipc.c | 3 + testcases/t/177-bar-config.t | 11 ++- testcases/t/201-config-parser.t | 2 +- 12 files changed, 138 insertions(+), 84 deletions(-) diff --git a/docs/ipc b/docs/ipc index 913899cc..85e5e77e 100644 --- a/docs/ipc +++ b/docs/ipc @@ -494,6 +494,8 @@ font (string):: The font to use for text on the bar. workspace_buttons (boolean):: Display workspace buttons or not? Defaults to true. +binding_mode_indicator (boolean):: + Display the mode indicator or not? Defaults to true. verbose (boolean):: Should the bar enable verbose output for debugging? Defaults to false. colors (map):: @@ -539,6 +541,7 @@ urgent_workspace_text/urgent_workspace_bar:: "status_command": "i3status", "font": "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1", "workspace_buttons": true, + "binding_mode_indicator": true, "verbose": false, "colors": { "background": "#c0c0c0", diff --git a/docs/userguide b/docs/userguide index ae3bda2e..0cc147ca 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1180,11 +1180,32 @@ workspace_buttons -------------------------- *Example*: --------------------- +------------------------ bar { workspace_buttons no } --------------------- +------------------------ + +=== Binding Mode indicator + +Specifies whether the current binding mode indicator should be shown or not. +This is useful if you want to hide the workspace buttons but still be able +to see the current binding mode indicator. +For an example of a +mode+ definition, see <>. + +The default is to show the mode indicator. + +*Syntax*: +------------------------------- +binding_mode_indicator +------------------------------- + +*Example*: +----------------------------- +bar { + binding_mode_indicator no +} +----------------------------- === Colors diff --git a/i3bar/include/config.h b/i3bar/include/config.h index 4c01d68c..c6486712 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -23,7 +23,8 @@ typedef struct config_t { position_t position; int verbose; struct xcb_color_strings_t colors; - int disable_ws; + bool disable_binding_mode_indicator; + bool disable_ws; char *bar_id; char *command; char *fontname; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index f5a2a342..5ac31b1f 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -193,6 +193,12 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in * */ static int config_boolean_cb(void *params_, int val) { + if (!strcmp(cur_key, "binding_mode_indicator")) { + DLOG("binding_mode_indicator = %d\n", val); + config.disable_binding_mode_indicator = !val; + return 1; + } + if (!strcmp(cur_key, "workspace_buttons")) { DLOG("workspace_buttons = %d\n", val); config.disable_ws = !val; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 36c2e470..f407c9b1 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1670,72 +1670,71 @@ void draw_bars(bool unhide) { MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2); } - if (config.disable_ws) { - continue; + if (!config.disable_ws) { + i3_ws *ws_walk; + TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { + DLOG("Drawing Button for WS %s at x = %d, len = %d\n", + i3string_as_utf8(ws_walk->name), i, ws_walk->name_width); + uint32_t fg_color = colors.inactive_ws_fg; + uint32_t bg_color = colors.inactive_ws_bg; + uint32_t border_color = colors.inactive_ws_border; + if (ws_walk->visible) { + if (!ws_walk->focused) { + fg_color = colors.active_ws_fg; + bg_color = colors.active_ws_bg; + border_color = colors.active_ws_border; + } else { + fg_color = colors.focus_ws_fg; + bg_color = colors.focus_ws_bg; + border_color = colors.focus_ws_border; + if (last_urgent_ws && strcmp(i3string_as_utf8(ws_walk->name), + last_urgent_ws) == 0) + walks_away = false; + } + } + if (ws_walk->urgent) { + DLOG("WS %s is urgent!\n", i3string_as_utf8(ws_walk->name)); + fg_color = colors.urgent_ws_fg; + bg_color = colors.urgent_ws_bg; + border_color = colors.urgent_ws_border; + unhide = true; + if (!ws_walk->focused) { + FREE(last_urgent_ws); + last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name)); + } + } + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; + uint32_t vals_border[] = { border_color, border_color }; + xcb_change_gc(xcb_connection, + outputs_walk->bargc, + mask, + vals_border); + xcb_rectangle_t rect_border = { i, 1, ws_walk->name_width + 10, font.height + 4 }; + xcb_poly_fill_rectangle(xcb_connection, + outputs_walk->buffer, + outputs_walk->bargc, + 1, + &rect_border); + uint32_t vals[] = { bg_color, bg_color }; + xcb_change_gc(xcb_connection, + outputs_walk->bargc, + mask, + vals); + xcb_rectangle_t rect = { i + 1, 2, ws_walk->name_width + 8, font.height + 2 }; + xcb_poly_fill_rectangle(xcb_connection, + outputs_walk->buffer, + outputs_walk->bargc, + 1, + &rect); + set_font_colors(outputs_walk->bargc, fg_color, bg_color); + draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, + i + 5, 3, ws_walk->name_width); + i += 10 + ws_walk->name_width + 1; + + } } - i3_ws *ws_walk; - - TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { - DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width); - uint32_t fg_color = colors.inactive_ws_fg; - uint32_t bg_color = colors.inactive_ws_bg; - uint32_t border_color = colors.inactive_ws_border; - if (ws_walk->visible) { - if (!ws_walk->focused) { - fg_color = colors.active_ws_fg; - bg_color = colors.active_ws_bg; - border_color = colors.active_ws_border; - } else { - fg_color = colors.focus_ws_fg; - bg_color = colors.focus_ws_bg; - border_color = colors.focus_ws_border; - if (last_urgent_ws && strcmp(i3string_as_utf8(ws_walk->name), last_urgent_ws) == 0) - walks_away = false; - } - } - if (ws_walk->urgent) { - DLOG("WS %s is urgent!\n", i3string_as_utf8(ws_walk->name)); - fg_color = colors.urgent_ws_fg; - bg_color = colors.urgent_ws_bg; - border_color = colors.urgent_ws_border; - unhide = true; - if (!ws_walk->focused) { - FREE(last_urgent_ws); - last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name)); - } - } - uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; - uint32_t vals_border[] = { border_color, border_color }; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals_border); - xcb_rectangle_t rect_border = { i, 1, ws_walk->name_width + 10, font.height + 4 }; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect_border); - uint32_t vals[] = { bg_color, bg_color }; - xcb_change_gc(xcb_connection, - outputs_walk->bargc, - mask, - vals); - xcb_rectangle_t rect = { i + 1, 2, ws_walk->name_width + 8, font.height + 2 }; - xcb_poly_fill_rectangle(xcb_connection, - outputs_walk->buffer, - outputs_walk->bargc, - 1, - &rect); - set_font_colors(outputs_walk->bargc, fg_color, bg_color); - draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 3, ws_walk->name_width); - i += 10 + ws_walk->name_width + 1; - - } - - if (binding.name) { - + if (binding.name && !config.disable_binding_mode_indicator) { uint32_t fg_color = colors.urgent_ws_fg; uint32_t bg_color = colors.urgent_ws_bg; uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; diff --git a/include/config.h b/include/config.h index c7479b3a..4267dcfe 100644 --- a/include/config.h +++ b/include/config.h @@ -267,6 +267,10 @@ struct Barconfig { * zero. */ bool hide_workspace_buttons; + /** Hide mode button? Configuration option is 'binding_mode_indicator no' + * but we invert the bool for the same reason as hide_workspace_buttons.*/ + bool hide_binding_mode_indicator; + /** Enable verbose mode? Useful for debugging purposes. */ bool verbose; diff --git a/include/config_directives.h b/include/config_directives.h index f9b7a47f..9569a7b0 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -74,6 +74,7 @@ CFGFUN(bar_socket_path, const char *socket_path); CFGFUN(bar_tray_output, const char *output); 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_finish); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index fd13797b..dfd6401d 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -345,20 +345,21 @@ state BAR: error -> '#' -> BAR_IGNORE_LINE 'set' -> BAR_IGNORE_LINE - 'i3bar_command' -> BAR_BAR_COMMAND - 'status_command' -> BAR_STATUS_COMMAND - 'socket_path' -> BAR_SOCKET_PATH - 'mode' -> BAR_MODE - 'hidden_state' -> BAR_HIDDEN_STATE - 'id' -> BAR_ID - 'modifier' -> BAR_MODIFIER - 'position' -> BAR_POSITION - 'output' -> BAR_OUTPUT - 'tray_output' -> BAR_TRAY_OUTPUT - 'font' -> BAR_FONT - 'workspace_buttons' -> BAR_WORKSPACE_BUTTONS - 'verbose' -> BAR_VERBOSE - 'colors' -> BAR_COLORS_BRACE + 'i3bar_command' -> BAR_BAR_COMMAND + 'status_command' -> BAR_STATUS_COMMAND + 'socket_path' -> BAR_SOCKET_PATH + 'mode' -> BAR_MODE + 'hidden_state' -> BAR_HIDDEN_STATE + 'id' -> BAR_ID + 'modifier' -> BAR_MODIFIER + 'position' -> BAR_POSITION + 'output' -> BAR_OUTPUT + 'tray_output' -> BAR_TRAY_OUTPUT + 'font' -> BAR_FONT + 'binding_mode_indicator' -> BAR_BINDING_MODE_INDICATOR + 'workspace_buttons' -> BAR_WORKSPACE_BUTTONS + 'verbose' -> BAR_VERBOSE + 'colors' -> BAR_COLORS_BRACE '}' -> call cfg_bar_finish(); INITIAL @@ -411,6 +412,10 @@ state BAR_FONT: font = string -> call cfg_bar_font($font); BAR +state BAR_BINDING_MODE_INDICATOR: + value = word + -> call cfg_bar_binding_mode_indicator($value); BAR + state BAR_WORKSPACE_BUTTONS: value = word -> call cfg_bar_workspace_buttons($value); BAR diff --git a/src/config_directives.c b/src/config_directives.c index 0fac7006..2bd4c90f 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -550,6 +550,10 @@ CFGFUN(bar_status_command, const char *command) { current_bar.status_command = sstrdup(command); } +CFGFUN(bar_binding_mode_indicator, const char *value) { + current_bar.hide_binding_mode_indicator = !eval_boolstr(value); +} + CFGFUN(bar_workspace_buttons, const char *value) { current_bar.hide_workspace_buttons = !eval_boolstr(value); } diff --git a/src/ipc.c b/src/ipc.c index 4c41465b..a928dba9 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -686,6 +686,9 @@ IPC_HANDLER(get_bar_config) { ystr("workspace_buttons"); y(bool, !config->hide_workspace_buttons); + ystr("binding_mode_indicator"); + y(bool, !config->hide_binding_mode_indicator); + ystr("verbose"); y(bool, config->verbose); diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index 762e52b8..8675dd71 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'); +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'); @@ -85,7 +86,8 @@ $config = <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'); +ok(!$bar_config->{binding_mode_indicator}, 'mode indicator disabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); @@ -230,7 +234,8 @@ $config = <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'); +ok($bar_config->{binding_mode_indicator}, 'mode indicator enabled'); is($bar_config->{mode}, 'dock', 'dock mode'); is($bar_config->{position}, 'top', 'position top'); is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 06588b11..55239c62 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -627,7 +627,7 @@ EOT $expected = <<'EOT'; cfg_bar_output(LVDS-1) -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'binding_mode_indicator', 'workspace_buttons', 'verbose', 'colors', '}' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: bar { ERROR: CONFIG: Line 2: output LVDS-1 From 8d38529bcdafd331a618552399a04a30b71a57c3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 24 Sep 2013 21:53:48 +0200 Subject: [PATCH 18/67] tests: move set_wm_class to X11::XCB::Window This code was duplicated way too often for a long time :) --- testcases/Makefile.PL | 2 +- testcases/t/119-match.t | 38 +++------------------ testcases/t/165-for_window.t | 36 +++++-------------- testcases/t/166-assign.t | 23 +------------ testcases/t/173-regress-focus-assign.t | 23 +------------ testcases/t/190-scratchpad-diff-ws.t | 33 +----------------- testcases/t/203-regress-assign-and-move.t | 21 +----------- testcases/t/204-regress-scratchpad-move.t | 32 +---------------- testcases/t/208-regress-floating-criteria.t | 22 +----------- testcases/t/211-regress-urgency-assign.t | 23 +------------ 10 files changed, 21 insertions(+), 232 deletions(-) diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 28036d1f..1a9b19cf 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -9,7 +9,7 @@ WriteMakefile( PREREQ_PM => { 'AnyEvent' => 0, 'AnyEvent::I3' => '0.14', - 'X11::XCB' => '0.03', + 'X11::XCB' => '0.09', 'Inline' => 0, 'ExtUtils::PkgConfig' => 0, 'Test::More' => '0.94', diff --git a/testcases/t/119-match.t b/testcases/t/119-match.t index 7ac622c7..0aaeddd7 100644 --- a/testcases/t/119-match.t +++ b/testcases/t/119-match.t @@ -17,7 +17,6 @@ # Tests all kinds of matching methods # use i3test; -use X11::XCB qw(PROP_MODE_REPLACE); my $tmp = fresh_workspace; @@ -61,39 +60,10 @@ is_num_children($tmp, 0, 'window killed'); $tmp = fresh_workspace; -# TODO: move to X11::XCB -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} - -sub open_special { - my %args = @_; - my $wm_class = delete($args{wm_class}) || 'special'; - - return open_window( - %args, - before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) }, - ); -} - -my $left = open_special(name => 'left'); +my $left = open_window(wm_class => 'special', name => 'left'); ok($left->mapped, 'left window mapped'); -my $right = open_special(name => 'right'); +my $right = open_window(wm_class => 'special', name => 'right'); ok($right->mapped, 'right window mapped'); # two windows should be here @@ -111,7 +81,7 @@ is_num_children($tmp, 1, 'one window still there'); $tmp = fresh_workspace; -$left = open_special(name => 'left', wm_class => 'special7'); +$left = open_window(name => 'left', wm_class => 'special7'); ok($left->mapped, 'left window mapped'); is_num_children($tmp, 1, 'window opened'); @@ -125,7 +95,7 @@ is_num_children($tmp, 0, 'window killed'); $tmp = fresh_workspace; -$left = open_special(name => 'ä 3', wm_class => 'special7'); +$left = open_window(name => 'ä 3', wm_class => 'special7'); ok($left->mapped, 'left window mapped'); is_num_children($tmp, 1, 'window opened'); diff --git a/testcases/t/165-for_window.t b/testcases/t/165-for_window.t index b01de91d..ada7c5c5 100644 --- a/testcases/t/165-for_window.t +++ b/testcases/t/165-for_window.t @@ -49,29 +49,9 @@ wait_for_unmap $window; cmp_ok(@content, '==', 0, 'no more nodes'); diag('content = '. Dumper(\@content)); - -# TODO: move this to X11::XCB::Window -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} - $window = open_window( name => 'Borderless window', - before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') }, + wm_class => 'borderless', ); @content = @{get_ws_content($tmp)}; @@ -190,7 +170,7 @@ $tmp = fresh_workspace; $window = open_window( name => 'usethis', - before_map => sub { set_wm_class($_->id, 'borderless', 'borderless') }, + wm_class => 'borderless', ); @content = @{get_ws_content($tmp)}; @@ -208,8 +188,7 @@ sync_with_i3; cmp_ok(@content, '==', 0, 'no nodes on this workspace now'); $window->_create; - -set_wm_class($window->id, 'borderless', 'borderless'); +$window->wm_class('borderless'); $window->name('notthis'); $window->map; wait_for_map $window; @@ -238,7 +217,8 @@ $tmp = fresh_workspace; $window = open_window( name => 'usethis', - before_map => sub { set_wm_class($_->id, 'bar', 'foo') }, + wm_class => 'bar', + instance => 'foo', ); @content = @{get_ws_content($tmp)}; @@ -264,7 +244,8 @@ $tmp = fresh_workspace; $window = open_window( name => 'usethis', - before_map => sub { set_wm_class($_->id, 'bar', 'foo') }, + wm_class => 'bar', + instance => 'foo', ); @content = @{get_ws_content($tmp)}; @@ -292,7 +273,8 @@ $tmp = fresh_workspace; $window = open_window( name => 'usethis', - before_map => sub { set_wm_class($_->id, 'bar', 'foo') }, + wm_class => 'bar', + instance => 'foo', ); @content = @{get_ws_content($tmp)}; diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index 1cee1ebb..355058dd 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -17,37 +17,16 @@ # Tests if assignments work # use i3test i3_autostart => 0; -use X11::XCB qw(PROP_MODE_REPLACE); - -# TODO: move to X11::XCB -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} sub open_special { my %args = @_; - my $wm_class = delete($args{wm_class}) || 'special'; $args{name} //= 'special window'; # We use dont_map because i3 will not map the window on the current # workspace. Thus, open_window would time out in wait_for_map (2 seconds). my $window = open_window( %args, - before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) }, + wm_class => 'special', dont_map => 1, ); $window->map; diff --git a/testcases/t/173-regress-focus-assign.t b/testcases/t/173-regress-focus-assign.t index aff3fe3a..b010963b 100644 --- a/testcases/t/173-regress-focus-assign.t +++ b/testcases/t/173-regress-focus-assign.t @@ -18,37 +18,16 @@ # assigned to an invisible workspace # use i3test i3_autostart => 0; -use X11::XCB qw(PROP_MODE_REPLACE); - -# TODO: move to X11::XCB -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} sub open_special { my %args = @_; - my $wm_class = delete($args{wm_class}) || 'special'; $args{name} //= 'special window'; # We use dont_map because i3 will not map the window on the current # workspace. Thus, open_window would time out in wait_for_map (2 seconds). my $window = open_window( %args, - before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) }, + wm_class => 'special', dont_map => 1, ); $window->map; diff --git a/testcases/t/190-scratchpad-diff-ws.t b/testcases/t/190-scratchpad-diff-ws.t index 5451f482..644aafb1 100644 --- a/testcases/t/190-scratchpad-diff-ws.t +++ b/testcases/t/190-scratchpad-diff-ws.t @@ -18,44 +18,13 @@ # window is shown on another workspace. # use i3test; -use List::Util qw(first); -use X11::XCB qw(:all); my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; -# TODO: move to X11::XCB -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} - -sub open_special { - my %args = @_; - my $wm_class = delete($args{wm_class}) || 'special'; - - return open_window( - %args, - before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) }, - ); -} - my $win = open_window; -my $scratch = open_special; +my $scratch = open_window(wm_class => 'special'); cmd '[class="special"] move scratchpad'; is_num_children($tmp, 1, 'one window on current ws'); diff --git a/testcases/t/203-regress-assign-and-move.t b/testcases/t/203-regress-assign-and-move.t index bf3df91e..51a1676f 100644 --- a/testcases/t/203-regress-assign-and-move.t +++ b/testcases/t/203-regress-assign-and-move.t @@ -19,7 +19,6 @@ # Ticket: #909 # Bug still in: 4.4-69-g6856b23 use i3test i3_autostart => 0; -use X11::XCB qw(:all); my $config = <atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} - # We use dont_map because i3 will not map the window on the current # workspace. Thus, open_window would time out in wait_for_map (2 seconds). my $window = open_window( - before_map => sub { set_wm_class($_->id, '__i3-test-window', '__i3-test-window') }, + wm_class => '__i3-test-window', dont_map => 1, ); $window->map; diff --git a/testcases/t/204-regress-scratchpad-move.t b/testcases/t/204-regress-scratchpad-move.t index 8c307aa5..0782da38 100644 --- a/testcases/t/204-regress-scratchpad-move.t +++ b/testcases/t/204-regress-scratchpad-move.t @@ -20,41 +20,11 @@ # Ticket: #913 # Bug still in: 4.4-97-gf767ac3 use i3test; -use X11::XCB qw(:all); - -# TODO: move to X11::XCB -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} - -sub open_special { - my %args = @_; - my $wm_class = delete($args{wm_class}) || 'special'; - - return open_window( - %args, - before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) }, - ); -} my $tmp = fresh_workspace; # Open a new window which we can identify later on based on its WM_CLASS. -my $scratch = open_special; +my $scratch = open_window(wm_class => 'special'); my $tmp2 = fresh_workspace; diff --git a/testcases/t/208-regress-floating-criteria.t b/testcases/t/208-regress-floating-criteria.t index 09a29fd8..a13e0ad3 100644 --- a/testcases/t/208-regress-floating-criteria.t +++ b/testcases/t/208-regress-floating-criteria.t @@ -19,7 +19,6 @@ # Ticket: #1027 # Bug still in: 4.5.1-90-g6582da9 use i3test i3_autostart => 0; -use X11::XCB qw(PROP_MODE_REPLACE); my $config = <<'EOT'; # i3 config file (v4) @@ -31,28 +30,9 @@ EOT my $pid = launch_with_config($config); -# TODO: move this to X11::XCB::Window -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} - my $window = open_window( name => 'Borderless window', - before_map => sub { set_wm_class($_->id, 'special', 'special') }, + wm_class => 'special', dont_map => 1, ); $window->map; diff --git a/testcases/t/211-regress-urgency-assign.t b/testcases/t/211-regress-urgency-assign.t index a90703ee..57d8b434 100644 --- a/testcases/t/211-regress-urgency-assign.t +++ b/testcases/t/211-regress-urgency-assign.t @@ -21,37 +21,16 @@ # Ticket: #1086 # Bug still in: 4.6-62-g7098ef6 use i3test i3_autostart => 0; -use X11::XCB qw(:all); - -# TODO: move to X11::XCB -sub set_wm_class { - my ($id, $class, $instance) = @_; - - # Add a _NET_WM_STRUT_PARTIAL hint - my $atomname = $x->atom(name => 'WM_CLASS'); - my $atomtype = $x->atom(name => 'STRING'); - - $x->change_property( - PROP_MODE_REPLACE, - $id, - $atomname->id, - $atomtype->id, - 8, - length($class) + length($instance) + 2, - "$instance\x00$class\x00" - ); -} sub open_special { my %args = @_; - my $wm_class = delete($args{wm_class}) || 'special'; $args{name} //= 'special window'; # We use dont_map because i3 will not map the window on the current # workspace. Thus, open_window would time out in wait_for_map (2 seconds). my $window = open_window( %args, - before_map => sub { set_wm_class($_->id, $wm_class, $wm_class) }, + wm_class => 'special', dont_map => 1, ); $window->add_hint('urgency'); From 0bfcf1a76204a92c908eed67b5a7659c416bd040 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 25 Sep 2013 19:33:28 +0200 Subject: [PATCH 19/67] Improve error message when $XDG_RUNTIME_DIR is not writable --- libi3/get_process_filename.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libi3/get_process_filename.c b/libi3/get_process_filename.c index 630e3d1c..941d4439 100644 --- a/libi3/get_process_filename.c +++ b/libi3/get_process_filename.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "libi3.h" @@ -35,6 +36,9 @@ char *get_process_filename(const char *prefix) { struct stat buf; if (stat(dir, &buf) != 0) { if (mkdir(dir, 0700) == -1) { + warn("Could not mkdir(%s)", dir); + errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'", + getenv("XDG_RUNTIME_DIR")); perror("mkdir()"); return NULL; } From 5baada653263cd4332b31c016d7a19425cdd4469 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Wed, 25 Sep 2013 17:17:38 -0400 Subject: [PATCH 20/67] reduce some yajl boilerplate This patch introduces a yerror() macro in src/commands.c and also removes some unused yajl helper macros from src/config_directives.c. Signed-off-by: Vivien Didelot --- src/commands.c | 64 +++++++++++------------------------------ src/config_directives.c | 10 ------- 2 files changed, 16 insertions(+), 58 deletions(-) diff --git a/src/commands.c b/src/commands.c index f4274246..8f355795 100644 --- a/src/commands.c +++ b/src/commands.c @@ -24,6 +24,14 @@ y(bool, success); \ y(map_close); \ } while (0) +#define yerror(message) do { \ + y(map_open); \ + ystr("success"); \ + y(bool, false); \ + ystr("error"); \ + ystr(message); \ + y(map_close); \ +} while (0) /** When the command did not include match criteria (!), we use the currently * focused container. Do not confuse this case with a command which included @@ -441,12 +449,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { ws = workspace_back_and_forth_get(); if (ws == NULL) { - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); - ystr("No workspace was previously active."); - y(map_close); + yerror("No workspace was previously active."); return; } @@ -535,13 +538,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { parsed_num < 0 || endptr == which) { LOG("Could not parse initial part of \"%s\" as a number.\n", which); - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); // TODO: better error message - ystr("Could not parse number"); - y(map_close); + yerror("Could not parse number"); return; } @@ -939,13 +937,8 @@ void cmd_workspace_number(I3_CMD, char *which) { parsed_num < 0 || endptr == which) { LOG("Could not parse initial part of \"%s\" as a number.\n", which); - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); // TODO: better error message - ystr("Could not parse number"); - y(map_close); + yerror("Could not parse number"); return; } @@ -1439,12 +1432,7 @@ void cmd_focus(I3_CMD) { ELOG("You have to specify which window/container should be focused.\n"); ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n"); - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); - ystr("You have to specify which window/container should be focused"); - y(map_close); + yerror("You have to specify which window/container should be focused"); return; } @@ -1750,12 +1738,7 @@ void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) { if (!con_is_floating(focused)) { ELOG("Cannot change position. The window/container is not floating\n"); - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); - ystr("Cannot change position. The window/container is not floating."); - y(map_close); + yerror("Cannot change position. The window/container is not floating."); return; } @@ -1790,12 +1773,7 @@ void cmd_move_window_to_center(I3_CMD, char *method) { if (!con_is_floating(focused)) { ELOG("Cannot change position. The window/container is not floating\n"); - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); - ystr("Cannot change position. The window/container is not floating."); - y(map_close); + yerror("Cannot change position. The window/container is not floating."); return; } @@ -1890,13 +1868,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { if (!workspace) { // TODO: we should include the old workspace name here and use yajl for // generating the reply. - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); // TODO: better error message - ystr("Old workspace not found"); - y(map_close); + yerror("Old workspace not found"); return; } @@ -1908,13 +1881,8 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { if (check_dest != NULL) { // TODO: we should include the new workspace name here and use yajl for // generating the reply. - y(map_open); - ystr("success"); - y(bool, false); - ystr("error"); // TODO: better error message - ystr("New workspace already exists"); - y(map_close); + yerror("New workspace already exists"); return; } diff --git a/src/config_directives.c b/src/config_directives.c index 2bd4c90f..b28ad49d 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -14,16 +14,6 @@ #include "all.h" -// Macros to make the YAJL API a bit easier to use. -#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__) -#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str)) -#define ysuccess(success) do { \ - y(map_open); \ - ystr("success"); \ - y(bool, success); \ - y(map_close); \ -} while (0) - /******************************************************************************* * Criteria functions. ******************************************************************************/ From dbec5eb90585bc22752331f51d8a6bc90d21889c Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 24 Sep 2013 15:46:58 +0200 Subject: [PATCH 21/67] Fix keyboard and mouse resize in nested containers fixes #1084 fixes #1085 --- include/resize.h | 2 + src/click.c | 56 +++++++++++++------------- src/commands.c | 103 +++++++++++++++++------------------------------ src/resize.c | 48 ++++++++++++++++++++++ 4 files changed, 114 insertions(+), 95 deletions(-) diff --git a/include/resize.h b/include/resize.h index fa0216c8..ae26ee99 100644 --- a/include/resize.h +++ b/include/resize.h @@ -10,6 +10,8 @@ #ifndef I3_RESIZE_H #define I3_RESIZE_H +bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction); + int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event); #endif diff --git a/src/click.c b/src/click.c index 3022c248..340c8414 100644 --- a/src/click.c +++ b/src/click.c @@ -28,45 +28,43 @@ typedef enum { CLICK_BORDER = 0, CLICK_DECORATION = 1, CLICK_INSIDE = 2 } click_ */ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press_event_t *event) { DLOG("border = %d, con = %p\n", border, con); - char way = (border == BORDER_TOP || border == BORDER_LEFT ? 'p' : 'n'); - orientation_t orientation = (border == BORDER_TOP || border == BORDER_BOTTOM ? VERT : HORIZ); - - /* look for a parent container with the right orientation */ - Con *first = NULL, *second = NULL; - Con *resize_con = con; - while (resize_con->type != CT_WORKSPACE && - resize_con->type != CT_FLOATING_CON && - con_orientation(resize_con->parent) != orientation) - resize_con = resize_con->parent; - - DLOG("resize_con = %p\n", resize_con); - if (resize_con->type != CT_WORKSPACE && - resize_con->type != CT_FLOATING_CON && - con_orientation(resize_con->parent) == orientation) { - first = resize_con; - second = (way == 'n') ? TAILQ_NEXT(first, nodes) : TAILQ_PREV(first, nodes_head, nodes); - if (second == TAILQ_END(&(first->nodes_head))) { - second = NULL; - } - else if (way == 'p') { - Con *tmp = first; - first = second; - second = tmp; - } - DLOG("first = %p, second = %p, resize_con = %p\n", - first, second, resize_con); + Con *second = NULL; + Con *first = con; + direction_t search_direction; + switch (border) { + case BORDER_LEFT: + search_direction = D_LEFT; + break; + case BORDER_RIGHT: + search_direction = D_RIGHT; + break; + case BORDER_TOP: + search_direction = D_UP; + break; + case BORDER_BOTTOM: + search_direction = D_DOWN; + break; } - if (first == NULL || second == NULL) { - DLOG("Resize not possible\n"); + bool res = resize_find_tiling_participants(&first, &second, search_direction); + if (!res) { + LOG("No second container in this direction found.\n"); return false; } assert(first != second); assert(first->parent == second->parent); + /* The first container should always be in front of the second container */ + if (search_direction == D_UP || search_direction == D_LEFT) { + Con *tmp = first; + first = second; + second = tmp; + } + /* We modify the X/Y position in the event so that the divider line is at * the actual position of the border, not at the position of the click. */ + const orientation_t orientation = ((border == BORDER_LEFT || border == BORDER_RIGHT) ? HORIZ : VERT); if (orientation == HORIZ) event->root_x = second->rect.x; else event->root_y = second->rect.y; diff --git a/src/commands.c b/src/commands.c index 8f355795..9631923d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -616,79 +616,50 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { LOG("tiling resize\n"); - /* get the appropriate current container (skip stacked/tabbed cons) */ - Con *other = NULL; - double percentage = 0; - while (current->parent->layout == L_STACKED || - current->parent->layout == L_TABBED) - current = current->parent; + Con *second = NULL; + Con *first = current; + direction_t search_direction; + if (!strcmp(direction, "left")) + search_direction = D_LEFT; + else if (!strcmp(direction, "right")) + search_direction = D_RIGHT; + else if (!strcmp(direction, "up")) + search_direction = D_UP; + else + search_direction = D_DOWN; - /* Then further go up until we find one with the matching orientation. */ - orientation_t search_orientation = - (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT); - - do { - if (con_orientation(current->parent) != search_orientation) { - current = current->parent; - continue; - } - - /* get the default percentage */ - int children = con_num_children(current->parent); - LOG("ins. %d children\n", children); - percentage = 1.0 / children; - LOG("default percentage = %f\n", percentage); - - orientation_t orientation = con_orientation(current->parent); - - if ((orientation == HORIZ && - (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) || - (orientation == VERT && - (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) { - LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", - (orientation == HORIZ ? "horizontal" : "vertical")); - ysuccess(false); - return false; - } - - if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) { - other = TAILQ_PREV(current, nodes_head, nodes); - } else { - other = TAILQ_NEXT(current, nodes); - } - if (other == TAILQ_END(workspaces)) { - LOG("No other container in this direction found, trying to look further up in the tree...\n"); - current = current->parent; - continue; - } - break; - } while (current->type != CT_WORKSPACE && - current->type != CT_FLOATING_CON); - - if (other == NULL) { - LOG("No other container in this direction found, trying to look further up in the tree...\n"); + bool res = resize_find_tiling_participants(&first, &second, search_direction); + if (!res) { + LOG("No second container in this direction found.\n"); ysuccess(false); return false; } - LOG("other->percent = %f\n", other->percent); - LOG("current->percent before = %f\n", current->percent); - if (current->percent == 0.0) - current->percent = percentage; - if (other->percent == 0.0) - other->percent = percentage; - double new_current_percent = current->percent + ((double)ppt / 100.0); - double new_other_percent = other->percent - ((double)ppt / 100.0); - LOG("new_current_percent = %f\n", new_current_percent); - LOG("new_other_percent = %f\n", new_other_percent); + /* get the default percentage */ + int children = con_num_children(first->parent); + LOG("ins. %d children\n", children); + double percentage = 1.0 / children; + LOG("default percentage = %f\n", percentage); + + /* resize */ + LOG("second->percent = %f\n", second->percent); + LOG("first->percent before = %f\n", first->percent); + if (first->percent == 0.0) + first->percent = percentage; + if (second->percent == 0.0) + second->percent = percentage; + double new_first_percent = first->percent + ((double)ppt / 100.0); + double new_second_percent = second->percent - ((double)ppt / 100.0); + LOG("new_first_percent = %f\n", new_first_percent); + LOG("new_second_percent = %f\n", new_second_percent); /* Ensure that the new percentages are positive and greater than * 0.05 to have a reasonable minimum size. */ - if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) && - definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) { - current->percent += ((double)ppt / 100.0); - other->percent -= ((double)ppt / 100.0); - LOG("current->percent after = %f\n", current->percent); - LOG("other->percent after = %f\n", other->percent); + if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) && + definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) { + first->percent += ((double)ppt / 100.0); + second->percent -= ((double)ppt / 100.0); + LOG("first->percent after = %f\n", first->percent); + LOG("second->percent after = %f\n", second->percent); } else { LOG("Not resizing, already at minimum size\n"); } diff --git a/src/resize.c b/src/resize.c index 268dc3fb..f65ce88e 100644 --- a/src/resize.c +++ b/src/resize.c @@ -51,6 +51,54 @@ DRAGGING_CB(resize_callback) { xcb_flush(conn); } +bool resize_find_tiling_participants(Con **current, Con **other, direction_t direction) { + DLOG("Find two participants for resizing container=%p in direction=%i\n", other, direction); + Con *first = *current; + Con *second = NULL; + if (first == NULL) { + DLOG("Current container is NULL, aborting.\n"); + return false; + } + + /* Go up in the tree and search for a container to resize */ + const orientation_t search_orientation = ((direction == D_LEFT || direction == D_RIGHT) ? HORIZ : VERT); + const bool dir_backwards = (direction == D_UP || direction == D_LEFT); + while (first->type != CT_WORKSPACE && + first->type != CT_FLOATING_CON && + second == NULL) { + /* get the appropriate first container with the matching + * orientation (skip stacked/tabbed cons) */ + if ((con_orientation(first->parent) != search_orientation) || + (first->parent->layout == L_STACKED) || + (first->parent->layout == L_TABBED)) { + first = first->parent; + continue; + } + + /* get the counterpart for this resizement */ + if (dir_backwards) { + second = TAILQ_PREV(first, nodes_head, nodes); + } else { + second = TAILQ_NEXT(first, nodes); + } + + if (second == NULL) { + DLOG("No second container in this direction found, trying to look further up in the tree...\n"); + first = first->parent; + } + } + + DLOG("Found participants: first=%p and second=%p.", first, second); + *current = first; + *other = second; + if (first == NULL || second == NULL) { + DLOG("Could not find two participants for this resize request.\n"); + return false; + } + + return true; +} + int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event) { DLOG("resize handler\n"); From a49dfaf26c5ad9b899c80a04c739571539d7efb6 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Fri, 27 Sep 2013 15:55:49 +0200 Subject: [PATCH 22/67] libi3/font: Draw the text at the expected place When drawing a text with Pango, shift it to the top according to the top if the glyph if taller than expected We always shift of (height - savedHeight) which is a no-op for normal glyphs Signed-off-by: Quentin Glidic --- libi3/font.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libi3/font.c b/libi3/font.c index 8239b1f4..c57009c0 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -86,16 +86,20 @@ static void draw_text_pango(const char *text, size_t text_len, root_visual_type, x + max_width, y + savedFont->height); cairo_t *cr = cairo_create(surface); PangoLayout *layout = pango_cairo_create_layout(cr); + gint height; + pango_layout_set_font_description(layout, savedFont->specific.pango_desc); pango_layout_set_width(layout, max_width * PANGO_SCALE); pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_text(layout, text, text_len); + /* Do the drawing */ cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue); - cairo_move_to(cr, x, y); - pango_layout_set_text(layout, text, text_len); pango_cairo_update_layout(cr, layout); + pango_layout_get_pixel_size(layout, NULL, &height); + cairo_move_to(cr, x, y - (height - savedFont->height)); pango_cairo_show_layout(cr, layout); /* Free resources */ From 459490b67b6e2a1cf772360894e28aa54144ed51 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Wed, 25 Sep 2013 20:52:59 -0400 Subject: [PATCH 23/67] Add ability to escape out of a mouse-resize operation Implement #1074. drag_cancel grabs the keyboard and returns DRAG_CANCEL when the user presses any key during the grab. --- include/floating.h | 20 +++++++++---- src/floating.c | 70 ++++++++++++++++++++++++++++++++++++++-------- src/resize.c | 6 +++- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/include/floating.h b/include/floating.h index c8586527..04db1589 100644 --- a/include/floating.h +++ b/include/floating.h @@ -134,14 +134,22 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); #endif /** - * This function grabs your pointer 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). + * This is the return value of a drag operation like drag_pointer. DRAG_CANCEL + * will indicate the intention of the drag should not be carried out, or that + * the drag actions should be undone. * */ -void drag_pointer(Con *con, const xcb_button_press_event_t *event, +typedef enum { DRAG_SUCCESS = 0, DRAG_CANCEL } 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/src/floating.c b/src/floating.c index 97b7d884..a08b7b0f 100644 --- a/src/floating.c +++ b/src/floating.c @@ -441,8 +441,15 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { * after the user releases the mouse button */ tree_render(); + /* Store the initial rect in case of user cancel */ + Rect initial_rect = con->rect; + /* Drag the window */ - 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, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event); + + /* If the user cancelled, undo the changes. */ + if (drag_result == DRAG_CANCEL) + floating_reposition(con, initial_rect); /* If this is a scratchpad window, don't auto center it from now on. */ if (con->scratchpad_state == SCRATCHPAD_FRESH) @@ -546,7 +553,14 @@ void floating_resize_window(Con *con, const bool proportional, struct resize_window_callback_params params = { corner, proportional, event }; - drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms); + /* get the initial rect in case of 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); + + /* If the user cancels, undo the resize */ + if (drag_result == DRAG_CANCEL) + floating_reposition(con, initial_rect); /* If this is a scratchpad window, don't auto center it from now on. */ if (con->scratchpad_state == SCRATCHPAD_FRESH) @@ -554,14 +568,14 @@ void floating_resize_window(Con *con, const bool proportional, } /* - * This function grabs your pointer 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). + * 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). * */ -void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t +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) { uint32_t new_x, new_y; @@ -587,16 +601,37 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) { ELOG("Could not grab pointer\n"); - return; + return DRAG_CANCEL; } 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, NULL)) == NULL) { + ELOG("Could not grab keyboard\n"); + return DRAG_CANCEL; + } + + free(keyb_reply); + /* Go into our own event loop */ xcb_flush(conn); xcb_generic_event_t *inside_event, *last_motion_notify = NULL; bool loop_done = false; + /* The return value, set to DRAG_CANCEL on user cancel */ + drag_result_t drag_result = DRAG_SUCCESS; /* I’ve always wanted to have my own eventhandler… */ while (!loop_done && (inside_event = xcb_wait_for_event(conn))) { /* We now handle all events we can get using xcb_poll_for_event */ @@ -621,11 +656,20 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t break; case XCB_UNMAP_NOTIFY: - case XCB_KEY_PRESS: - case XCB_KEY_RELEASE: DLOG("Unmap-notify, aborting\n"); handle_event(type, inside_event); loop_done = true; + drag_result = DRAG_CANCEL; + break; + + case XCB_KEY_PRESS: + case XCB_KEY_RELEASE: + /* Cancel the drag if a key was pressed */ + DLOG("A key was pressed during drag, canceling."); + loop_done = true; + drag_result = DRAG_CANCEL; + + handle_event(type, inside_event); break; default: @@ -648,8 +692,12 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t FREE(last_motion_notify); } + xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + xcb_flush(conn); + + return drag_result; } /* diff --git a/src/resize.c b/src/resize.c index f65ce88e..764a485c 100644 --- a/src/resize.c +++ b/src/resize.c @@ -154,12 +154,16 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const struct callback_params params = { orientation, output, helpwin, &new_position }; - drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms); + drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); xcb_flush(conn); + /* User cancelled the drag so no action should be taken. */ + if (drag_result == DRAG_CANCEL) + return 0; + int pixels; if (orientation == HORIZ) pixels = (new_position - event->root_x); From 98c4cc46e4778a807f151b6a0ce26cc982fc168b Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 27 Sep 2013 11:03:52 +0200 Subject: [PATCH 24/67] Do not resize/reposition floating containers when moving them to scratchpad --- src/scratchpad.c | 8 +++++++- testcases/t/185-scratchpad.t | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/scratchpad.c b/src/scratchpad.c index ce3d9b9b..17df77dd 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -66,7 +66,13 @@ void scratchpad_move(Con *con) { * adjusted in size according to what the user specifies. */ if (con->scratchpad_state == SCRATCHPAD_NONE) { DLOG("This window was never used as a scratchpad before.\n"); - con->scratchpad_state = SCRATCHPAD_FRESH; + if (con == maybe_floating_con) { + DLOG("It was in floating mode before, set scratchpad state to changed.\n"); + con->scratchpad_state = SCRATCHPAD_CHANGED; + } else { + DLOG("It was in tiling mode before, set scratchpad state to fresh.\n"); + con->scratchpad_state = SCRATCHPAD_FRESH; + } } } diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t index 6ee877bd..5901f99c 100644 --- a/testcases/t/185-scratchpad.t +++ b/testcases/t/185-scratchpad.t @@ -446,4 +446,29 @@ is(get_focused($ws), $scratch, 'scratchpad is focused'); # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint +################################################################################ +# 14: Verify that 'move scratchpad' sends floating containers to scratchpad but +# does not resize/resposition the container on the next 'scratchpad show', i.e., +# i3 sets the scratchpad flag to SCRATCHPAD_CHANGED +################################################################################ + +clear_scratchpad; +$tmp = fresh_workspace; +open_window; + +($nodes, $focus) = get_ws_content($tmp); +is(scalar @$nodes, 1, 'precisely one window on current ws'); +is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none'); + +cmd 'floating toggle'; +cmd 'move scratchpad'; + +$__i3_scratch = get_ws('__i3_scratch'); +@scratch_nodes = @{$__i3_scratch->{floating_nodes}}; +is(scalar @scratch_nodes, 1, '__i3_scratch contains our window'); +($nodes, $focus) = get_ws_content($tmp); +is(scalar @$nodes, 0, 'no window on current ws anymore'); + +is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed'); + done_testing; From d661c1493c9218f48fee9b0fc71bc2006d55a3e2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 1 Oct 2013 07:21:40 +0200 Subject: [PATCH 25/67] Only abort resizing on KeyPress, not KeyRelease (Thanks vandannen) Otherwise, releasing a key that was used to trigger the resizing (e.g. the modifier key) needs to be pressed all the time. --- src/floating.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index a08b7b0f..ae1a9192 100644 --- a/src/floating.c +++ b/src/floating.c @@ -663,7 +663,6 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ break; case XCB_KEY_PRESS: - case XCB_KEY_RELEASE: /* Cancel the drag if a key was pressed */ DLOG("A key was pressed during drag, canceling."); loop_done = true; From 7f9d2ac9488cdb35508d7ad7c8171064e1ef5f00 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 13 Oct 2013 15:18:20 +0200 Subject: [PATCH 26/67] Add quoting for sample command The user's guide talks about renaming workspaces, for example to "2: mail", and a sample key binding for use with i3-input is supplied. However, this example lacks proper quoting for the format string, so that when workspace name with a space in it, like "2: mail", is given, the current workspace is renamed to "2:". This patch adds proper quoting. --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 0cc147ca..25ea5d54 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1590,7 +1590,7 @@ i3-msg 'rename workspace 5 to 6' i3-msg 'rename workspace 1 to "1: www"' i3-msg 'rename workspace "1: www" to "10: www"' i3-msg 'rename workspace to "2: mail" -bindsym $mod+r exec i3-input -F 'rename workspace to %s' -P 'New name: ' +bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: ' -------------------------------------------------------------------------- === Moving workspaces to a different screen From c142a4fa6c35cdc9c5e4f3db55d37b64f6f8d84e Mon Sep 17 00:00:00 2001 From: Peter Maatman Date: Fri, 11 Oct 2013 22:54:37 +0200 Subject: [PATCH 27/67] Update docs/hacking-howto to reflect parser changes --- docs/hacking-howto | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index 73f8e88a..bc59eaeb 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -97,21 +97,18 @@ Contains forward definitions for all public functions, as well as doxygen-compatible comments (so if you want to get a bit more of the big picture, either browse all header files or use doxygen if you prefer that). -src/cfgparse.l:: -Contains the lexer for i3’s configuration file, written for +flex(1)+. - -src/cfgparse.y:: -Contains the parser for i3’s configuration file, written for +bison(1)+. +src/config_parser.c:: +Contains a custom configuration parser. See src/command_parser.c for rationale + on why we use a custom parser. src/click.c:: Contains all functions which handle mouse button clicks (right mouse button clicks initiate resizing and thus are relatively complex). -src/cmdparse.l:: -Contains the lexer for i3 commands, written for +flex(1)+. - -src/cmdparse.y:: -Contains the parser for i3 commands, written for +bison(1)+. +src/command_parser.c:: +Contains a hand-written parser to parse commands (commands are what +you bind on keys and what you can send to i3 using the IPC interface, like +'move left' or 'workspace 4'). src/con.c:: Contains all functions which deal with containers directly (creating From f9d8a1b4d6dd097b0700afd863452d5b176d0f0e Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Fri, 11 Oct 2013 15:52:25 -0400 Subject: [PATCH 28/67] Testcases: ignore Xorg config dir when starting Xdummy Start Xdummy with '-configdir /dev/null' to avoid conflicting settings in xorg.conf.d. --- testcases/lib/StartXDummy.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/lib/StartXDummy.pm b/testcases/lib/StartXDummy.pm index f2ebcadd..592feb85 100644 --- a/testcases/lib/StartXDummy.pm +++ b/testcases/lib/StartXDummy.pm @@ -113,7 +113,7 @@ sub start_xdummy { # actual system X configuration. my $socket = fork_xserver($keep_xdummy_output, $displaynum, './Xdummy', ":$displaynum", '-config', '/dev/null', - '-nolisten', 'tcp'); + '-configdir', '/dev/null', '-nolisten', 'tcp'); push(@displays, ":$displaynum"); push(@sockets_waiting, $socket); $displaynum++; From eef9042713bd9b26c23e42f1bf07100113e1f599 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 11 Oct 2013 20:12:05 +0200 Subject: [PATCH 29/67] Fix endless loop when trying to kill a visible workspace This regression was introduced with commit 97b086efd9833d2a787e54417789b279143e43a6 fixes #1103 --- src/tree.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/tree.c b/src/tree.c index 4df9f593..65d709a5 100644 --- a/src/tree.c +++ b/src/tree.c @@ -229,11 +229,6 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool return false; } - if (workspace_is_visible(con)) { - DLOG("A visible workspace cannot be killed.\n"); - return false; - } - if (con->window != NULL) { if (kill_window != DONT_KILL_WINDOW) { x_window_kill(con->window->id, kill_window); @@ -369,6 +364,19 @@ void tree_close_con(kill_window_t kill_window) { assert(focused->type != CT_OUTPUT); assert(focused->type != CT_ROOT); + if (focused->type == CT_WORKSPACE) { + DLOG("Workspaces cannot be close, closing all children instead\n"); + Con *child, *nextchild; + for (child = TAILQ_FIRST(&(focused->nodes_head)); child; ) { + nextchild = TAILQ_NEXT(child, nodes); + DLOG("killing child=%p\n", child); + tree_close(child, kill_window, false, false); + child = nextchild; + } + + return; + } + /* Kill con */ tree_close(focused, kill_window, false, false); } From 1c0554564ac87e6c1bef9734abc63d0a01cf18fb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 13 Oct 2013 17:57:50 +0200 Subject: [PATCH 30/67] previous commit: extend test and code to also work with floating windows --- src/tree.c | 4 ++-- testcases/t/129-focus-after-close.t | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/tree.c b/src/tree.c index 65d709a5..836183ec 100644 --- a/src/tree.c +++ b/src/tree.c @@ -367,8 +367,8 @@ void tree_close_con(kill_window_t kill_window) { if (focused->type == CT_WORKSPACE) { DLOG("Workspaces cannot be close, closing all children instead\n"); Con *child, *nextchild; - for (child = TAILQ_FIRST(&(focused->nodes_head)); child; ) { - nextchild = TAILQ_NEXT(child, nodes); + for (child = TAILQ_FIRST(&(focused->focus_head)); child; ) { + nextchild = TAILQ_NEXT(child, focused); DLOG("killing child=%p\n", child); tree_close(child, kill_window, false, false); child = nextchild; diff --git a/testcases/t/129-focus-after-close.t b/testcases/t/129-focus-after-close.t index 8d9ccbb9..2ebfd610 100644 --- a/testcases/t/129-focus-after-close.t +++ b/testcases/t/129-focus-after-close.t @@ -143,6 +143,31 @@ sync_with_i3; ($nodes, $focus) = get_ws_content($tmp); is(scalar @$nodes, 0, 'workspace is empty'); +################################################################################ +# check if killing a workspace also closes floating windows. +################################################################################ + +$tmp = fresh_workspace; + +$window = open_window; +my $floating_window = open_floating_window; + +# one window opened on the current workspace +($nodes, $focus) = get_ws_content($tmp); +is(scalar @$focus, 2, 'workspace contains two nodes'); + +# focus the workspace +cmd "focus parent"; +cmd "focus parent"; + +# try to kill the workspace +cmd "kill"; +sync_with_i3; + +# the workspace should now be empty +($nodes, $focus) = get_ws_content($tmp); +is(scalar @$focus, 0, 'workspace is empty'); + ############################################################## # and now for something completely different: # check if the pointer position is relevant when restoring focus From 8fe17407ffff1d0bbd0aa7787b382c882370dd85 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Mon, 7 Oct 2013 12:06:28 -0400 Subject: [PATCH 31/67] i3-nagbar: Set button inner-width to the width of the label Use predict_text_width to find the width of the label and then account for right padding when calculating the width of the button. --- i3-nagbar/main.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index a9619f96..f7a44093 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -198,8 +198,12 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { 4 + 4, 4 + 4, rect.width - 4 - 4); /* render close button */ + const char *close_button_label = "X"; int line_width = 4; - int w = 20; + /* set width to the width of the label */ + int w = predict_text_width(i3string_from_utf8(close_button_label)); + /* account for left/right padding, which seems to be set to 8px (total) below */ + w += 8; int y = rect.width; uint32_t values[3]; values[0] = color_button_background; @@ -221,7 +225,8 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { values[0] = 1; set_font_colors(pixmap_gc, color_text, color_button_background); - draw_text_ascii("X", pixmap, pixmap_gc, y - w - line_width + w / 2 - 4, + /* the x term here seems to set left/right padding */ + draw_text_ascii(close_button_label, pixmap, pixmap_gc, y - w - line_width + w / 2 - 4, 4 + 4 - 1, rect.width - y + w + line_width - w / 2 + 4); y -= w; @@ -230,8 +235,10 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* render custom buttons */ line_width = 1; for (int c = 0; c < buttoncnt; c++) { - /* TODO: make w = text extents of the label */ - w = 100; + /* set w to the width of the label */ + w = predict_text_width(buttons[c].label); + /* account for left/right padding, which seems to be set to 12px (total) below */ + w += 12; y -= 30; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ color_button_background }); close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 }; @@ -252,6 +259,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { values[0] = color_text; values[1] = color_button_background; set_font_colors(pixmap_gc, color_text, color_button_background); + /* the x term seems to set left/right padding */ draw_text(buttons[c].label, pixmap, pixmap_gc, y - w - line_width + 6, 4 + 3, rect.width - y + w + line_width - 6); From bf1699e967d4ee072614338b9b645caa2d06794b Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 9 Oct 2013 22:30:41 +0200 Subject: [PATCH 32/67] Fix command parser: resizing tiling windows i3 would accept an invalid resize command like 'resize shrink width 10 px or' without specifying the ppt value, and then crash. This patch fixes the parser specification. --- parser-specs/commands.spec | 8 ++++---- testcases/t/187-commands-parser.t | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 2c640c65..e3da62c9 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -209,11 +209,11 @@ state RESIZE_TILING: -> call cmd_resize($way, $direction, $resize_px, "10") state RESIZE_TILING_OR: - 'ppt' - -> resize_ppt = word - -> - end + -> RESIZE_TILING_FINAL + +state RESIZE_TILING_FINAL: + 'ppt', end -> call cmd_resize($way, $direction, $resize_px, $resize_ppt) # rename workspace to diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index bbb89d93..5ee94f87 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -171,4 +171,19 @@ is(parser_calls('workspace "foo \"bar"'), 'cmd_workspace_name(foo "bar)', 'Command with escaped double quotes ok'); +################################################################################ +# 4: Verify that resize commands with a "px or ppt"-construction are parsed +# correctly +################################################################################ + +is(parser_calls("resize shrink width 10 px or"), + "ERROR: Expected one of these tokens: \n" . + "ERROR: Your command: resize shrink width 10 px or\n" . + "ERROR: ", + "error for resize command with incomplete 'or'-construction ok"); + +is(parser_calls("resize grow left 10 px or 20 ppt"), + "cmd_resize(grow, left, 10, 20)", + "resize command with 'or'-construction ok"); + done_testing; From 4622fd8703de3ec961cf47c4d67e10f210a80fe2 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 13 Oct 2013 22:52:01 +0200 Subject: [PATCH 33/67] Correct typos in user guide --- docs/userguide | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/userguide b/docs/userguide index 25ea5d54..a0862e64 100644 --- a/docs/userguide +++ b/docs/userguide @@ -252,7 +252,7 @@ workspace node. By default, the workspace node’s orientation is +horizontal+. Now you move one of these terminals down (+$mod+k+ by default). The workspace node’s orientation will be changed to +vertical+. The terminal window you moved down is directly attached to the workspace and appears on the bottom of the -screen. A new (horizontal) container was created to accomodate the other two +screen. A new (horizontal) container was created to accommodate the other two terminal windows. You will notice this when switching to tabbed mode (for example). You would end up having one tab called "another container" and the other one being the terminal window you moved down. @@ -446,7 +446,7 @@ New workspaces get a reasonable default orientation: Wide-screen monitors (anything higher than wide) get vertical orientation. With the +default_orientation+ configuration directive, you can override that -behaviour. +behavior. *Syntax*: ---------------------------------------------- @@ -834,7 +834,7 @@ popup_during_fullscreen smart When being in a tabbed or stacked container, the first container will be focused when you use +focus down+ on the last container -- the focus wraps. If however there is another stacked/tabbed container in that direction, focus will -be set on that container. This is the default behaviour so you can navigate to +be set on that container. This is the default behavior so you can navigate to all your windows without having to use +focus parent+. If you want the focus to *always* wrap and you are aware of using +focus @@ -900,7 +900,7 @@ workspace_auto_back_and_forth yes If an application on another workspace sets an urgency hint, switching to this workspace may lead to immediate focus of the application, which also means the -window decoration color would be immediately resetted to +client.focused+. This +window decoration color would be immediately reseted to +client.focused+. This may make it unnecessarily hard to tell which window originally raised the event. @@ -1575,7 +1575,7 @@ specify a default name if there's currently no workspace starting with a "1". You can rename workspaces. This might be useful to start with the default numbered workspaces, do your work, and rename the workspaces afterwards to reflect what’s actually on them. You can also omit the old name to rename -the currently focused workspace. This is handy if you wan't to use the +the currently focused workspace. This is handy if you want to use the rename command with +i3-input+. *Syntax*: From cf3da1c43317f9a0eaea259f64e5823797c0525f Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Tue, 15 Oct 2013 03:36:12 -0400 Subject: [PATCH 34/67] Testcases: remove "latest" if it is a symbolic link Test for the existence of the symlink to "latest" with the -l flag, which tests if the target is a symbolic link. Testing with the -e flag will fail in case the link points to a file that does not exist, which will occur if the test result directories are deleted by hand. --- testcases/complete-run.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index c6e74369..7ca89016 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -108,7 +108,7 @@ $outdir .= POSIX::strftime("%Y-%m-%d-%H-%M-%S-", localtime()); $outdir .= `git describe --tags`; chomp($outdir); mkdir($outdir) or die "Could not create $outdir"; -unlink("latest") if -e "latest"; +unlink("latest") if -l "latest"; symlink("$outdir", "latest") or die "Could not symlink latest to $outdir"; From 9adff26dcfbc14a0cfdaa0756d704c2e9f6f5b11 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Thu, 17 Oct 2013 07:20:35 -0400 Subject: [PATCH 35/67] Testcases: new-test prints usage without input Prevent the accidental creation of new tests with empty names by printing usage information for new-test when the test name would be empty. --- testcases/new-test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/testcases/new-test b/testcases/new-test index c2546158..14465d2d 100755 --- a/testcases/new-test +++ b/testcases/new-test @@ -15,6 +15,16 @@ use File::Basename qw(basename); use Getopt::Long; use v5.10; +my $usage = <<'EOF'; +Script to create a new testcase from a template. + + # Create (and edit) a new test for moving floating windows + ./new-test floating move + + # Create (and edit) a multi-monitor test for moving workspaces + ./new-test -m move workspaces +EOF + my $multi_monitor; my $result = GetOptions( @@ -24,6 +34,11 @@ my $result = GetOptions( my $testname = join(' ', @ARGV); $testname =~ s/ /-/g; +unless (length $testname) { + say $usage; + exit(0); +} + my $header = <<'EOF'; #!perl # vim:ts=4:sw=4:expandtab From 14b86acf40b53075d2ccd7cba47267f74c49c9ff Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Fri, 18 Oct 2013 20:02:17 -0400 Subject: [PATCH 36/67] Testcases: Use AnyEvent::I3 version 0.15 Use the latest version of AnyEvent::I3, version 0.15, which includes support for the `window` event tag. --- testcases/Makefile.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 1a9b19cf..6bc80d8f 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -8,7 +8,7 @@ WriteMakefile( MIN_PERL_VERSION => '5.010000', # 5.10.0 PREREQ_PM => { 'AnyEvent' => 0, - 'AnyEvent::I3' => '0.14', + 'AnyEvent::I3' => '0.15', 'X11::XCB' => '0.09', 'Inline' => 0, 'ExtUtils::PkgConfig' => 0, From e99158e419d4204bbce16ea5df249c630636ed3d Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Wed, 16 Oct 2013 22:44:56 -0400 Subject: [PATCH 37/67] Assigned windows open urgent when not visible When i3 begins to manage a window, if the window opens on a workspace that is not visible, the urgency hint on the newly managed window will be set. fixes #1088 --- src/manage.c | 12 +++- testcases/t/212-assign-urgency.t | 113 +++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 testcases/t/212-assign-urgency.t diff --git a/src/manage.c b/src/manage.c index 892ac4df..af9e8ef2 100644 --- a/src/manage.c +++ b/src/manage.c @@ -279,11 +279,17 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE | A_TO_OUTPUT))) { DLOG("Assignment matches (%p)\n", match); if (assignment->type == A_TO_WORKSPACE) { - nc = con_descend_tiling_focused(workspace_get(assignment->dest.workspace, NULL)); - DLOG("focused on ws %s: %p / %s\n", assignment->dest.workspace, nc, nc->name); + Con *assigned_ws = workspace_get(assignment->dest.workspace, NULL); + nc = con_descend_tiling_focused(assigned_ws); + DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name); if (nc->type == CT_WORKSPACE) nc = tree_open_con(nc, cwindow); - else nc = tree_open_con(nc->parent, cwindow); + else + nc = tree_open_con(nc->parent, cwindow); + + /* set the urgency hint on the window if the workspace is not visible */ + if (!workspace_is_visible(assigned_ws)) + urgency_hint = true; } /* TODO: handle assignments with type == A_TO_OUTPUT */ } else if (startup_ws) { diff --git a/testcases/t/212-assign-urgency.t b/testcases/t/212-assign-urgency.t new file mode 100644 index 00000000..f53eb7ef --- /dev/null +++ b/testcases/t/212-assign-urgency.t @@ -0,0 +1,113 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests if the urgency hint will be set appropriately when opening a window +# assigned to a workspace. +# +use i3test i3_autostart => 0; + +# Based on the eponymous function in t/166-assign.t +sub open_special { + my %args = @_; + $args{name} //= 'special window'; + + # We use dont_map because i3 will not map the window on the current + # workspace. Thus, open_window would time out in wait_for_map (2 seconds). + my $window = open_window( + %args, + wm_class => 'special', + dont_map => 1, + ); + $window->map; + return $window; +} + +##################################################################### +# start a window assigned to a non-visible workspace and see that the urgency +# hint is set. +##################################################################### + +my $config = <{urgent}, 'target workspace is urgent'); + +$window->destroy; + +exit_gracefully($pid); + + +##################################################################### +# start a window assigned to a visible workspace and see that the urgency hint +# is not set. +##################################################################### + +$config = <{urgent}, 'visible workspace is not urgent'); + +$window->destroy; + +exit_gracefully($pid); + +##################################################################### +# start a window assigned to a visible workspace on a different output and see +# that the urgency hint is not set. +##################################################################### + +$config = <{urgent}, 'target workspace is not urgent'); + +$window->destroy; + +exit_gracefully($pid); + +done_testing; From cce3c8066a7fbccf48b93a5a794e81068cc35f9e Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 20 Oct 2013 07:51:50 -0400 Subject: [PATCH 38/67] i3bar: Only configure tray on own outputs If the config specifies a `tray_output` not in the list of outputs over which this bar will span, do not initialize a tray for the bar. Fixes former behavior, which was to initialize the tray without showing the icons, causing disapearing tray icons in multi-monitor environments when `tray_output` isnt `output`. --- i3bar/src/xcb.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index f407c9b1..45648633 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1531,10 +1531,20 @@ void reconfig_windows(bool redraw_bars) { exit(EXIT_FAILURE); } - if (!tray_configured && - (!config.tray_output || - strcasecmp("none", config.tray_output) != 0)) { - init_tray(); + const char *tray_output = (config.tray_output ? config.tray_output : SLIST_FIRST(outputs)->name); + if (!tray_configured && strcasecmp(tray_output, "none") != 0) { + /* Configuration sanity check: ensure this i3bar instance handles the output on + * which the tray should appear (e.g. don’t initialize a tray if tray_output == + * VGA-1 but output == [HDMI-1]). + */ + i3_output *output; + SLIST_FOREACH(output, outputs, slist) { + if (strcasecmp(output->name, tray_output) == 0 || + (strcasecmp(tray_output, "primary") == 0 && output->primary)) { + init_tray(); + break; + } + } tray_configured = true; } } else { From 1110a1bed62ac21b7dc21fdab465e7ae66978b5a Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Wed, 23 Oct 2013 12:57:32 -0400 Subject: [PATCH 39/67] Add `.clang_complete` and `Xdummy.so` to gitignore Adds two files to gitignore. `.clang_complete` is created by the clang_complete vim plugin [https://github.com/Rip-Rip/clang_complete] `Xdummy.so` is created by compiling the Xdummy module. --- .gitignore | 1 + testcases/.gitignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0160a246..600b4fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ docs/*.html !/docs/refcard.html i3-command-parser.stamp i3-config-parser.stamp +.clang_complete diff --git a/testcases/.gitignore b/testcases/.gitignore index c11c5563..294f0dae 100644 --- a/testcases/.gitignore +++ b/testcases/.gitignore @@ -8,3 +8,4 @@ inc META.yml i3-cfg-for-* - +Xdummy.so From b6100dd7274a56b473927de9848bcff0e10b77d9 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 22 Oct 2013 13:17:23 +0200 Subject: [PATCH 40/67] Fix output retrieval for floating cons When focusing/moving to outputs, the method of getting the correct output for a given container fails if the container in question is floating and only partially mapped on an output screen. This patch introduces a fail-safe retrieval of the output for any container. --- src/commands.c | 18 ++++++++++++++---- testcases/t/502-focus-output.t | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/commands.c b/src/commands.c index 9631923d..32fdc7f1 100644 --- a/src/commands.c +++ b/src/commands.c @@ -77,6 +77,17 @@ static Output *get_output_from_string(Output *current_output, const char *output return output; } +/* + * Returns the output containing the given container. + */ +static Output *get_output_of_con(Con *con) { + Con *output_con = con_get_output(con); + Output *output = get_output_by_name(output_con->name); + assert(output != NULL); + + return output; +} + /* * Checks whether we switched to a new workspace and returns false in that case, * signaling that further workspace switching should be done by the calling function @@ -1049,7 +1060,7 @@ void cmd_move_con_to_output(I3_CMD, char *name) { // TODO: fix the handling of criteria TAILQ_FOREACH(current, &owindows, owindows) - current_output = get_output_containing(current->con->rect.x, current->con->rect.y); + current_output = get_output_of_con(current->con); assert(current_output != NULL); @@ -1131,8 +1142,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { owindow *current; TAILQ_FOREACH(current, &owindows, owindows) { - Output *current_output = get_output_containing(current->con->rect.x, - current->con->rect.y); + Output *current_output = get_output_of_con(current->con); if (!current_output) { ELOG("Cannot get current output. This is a bug in i3.\n"); ysuccess(false); @@ -1672,7 +1682,7 @@ void cmd_focus_output(I3_CMD, char *name) { Output *output; TAILQ_FOREACH(current, &owindows, owindows) - current_output = get_output_containing(current->con->rect.x, current->con->rect.y); + current_output = get_output_of_con(current->con); assert(current_output != NULL); output = get_output_from_string(current_output, name); diff --git a/testcases/t/502-focus-output.t b/testcases/t/502-focus-output.t index a6c5583f..8c1c36c5 100644 --- a/testcases/t/502-focus-output.t +++ b/testcases/t/502-focus-output.t @@ -71,6 +71,23 @@ is(focused_output, 'fake-1', 'focus on second output'); cmd 'focus output fake-0'; is(focused_output, 'fake-0', 'focus on first output'); +################################################################################ +# use 'focus output' and verify that i3 does not crash when the currently +# focused window is floating and is only partially mapped on an output screen +################################################################################ + +is(focused_output, 'fake-0', 'focus on first output'); + +my $floating_win = open_window; +cmd 'floating toggle'; +cmd 'move to absolute position -10 -10'; + +cmd 'focus output right'; +is(focused_output, 'fake-1', 'focus on second output'); + +cmd 'focus output fake-0'; +is(focused_output, 'fake-0', 'focus on first output'); + exit_gracefully($pid); done_testing; From 857fc0bf175fdbe90f59a6b80935d9b42dd81e83 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 22 Oct 2013 14:12:24 +0200 Subject: [PATCH 41/67] Update comment for the con_toggle_fullscreen method --- src/con.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/con.c b/src/con.c index 5b68481a..84063eba 100644 --- a/src/con.c +++ b/src/con.c @@ -569,8 +569,9 @@ void con_fix_percent(Con *con) { } /* - * Toggles fullscreen mode for the given container. Fullscreen mode will not be - * entered when there already is a fullscreen container on this workspace. + * Toggles fullscreen mode for the given container. If there already is a + * fullscreen container on this workspace, fullscreen will be disabled and then + * enabled for the container the user wants to have in fullscreen mode. * */ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { From f691a55923850a4d315450925fc98733d07b69c9 Mon Sep 17 00:00:00 2001 From: Baptiste Daroussin Date: Fri, 8 Nov 2013 21:13:35 +0100 Subject: [PATCH 42/67] Use _PATH_BSHELL to ensure using a bourne shell [Michael] This commit should fix problems with people using a non-bourne shell as login shell, e.g. fish or rc. AFAICT, $SHELL should only be used for interactive shells, but we just want a bourne shell, not an interactive shell. --- i3-nagbar/main.c | 10 ++-------- i3bar/src/child.c | 8 ++------ src/startup.c | 11 ++--------- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index f7a44093..0fa75f8e 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -95,15 +96,8 @@ static void start_application(const char *command) { /* Child process */ setsid(); if (fork() == 0) { - /* Stores the path of the shell */ - static const char *shell = NULL; - - if (shell == NULL) - if ((shell = getenv("SHELL")) == NULL) - shell = "/bin/sh"; - /* This is the child */ - execl(shell, shell, "-c", command, (void*)NULL); + execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void*)NULL); /* not reached */ } exit(0); diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 4e5e49c9..dce0218f 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "common.h" @@ -423,12 +424,7 @@ void start_child(char *command) { dup2(pipe_in[1], STDOUT_FILENO); dup2(pipe_out[0], STDIN_FILENO); - static const char *shell = NULL; - - if ((shell = getenv("SHELL")) == NULL) - shell = "/bin/sh"; - - execl(shell, shell, "-c", command, (char*) NULL); + execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char*) NULL); return; default: /* Parent-process. Reroute streams */ diff --git a/src/startup.c b/src/startup.c index ee51664f..85e5dbc2 100644 --- a/src/startup.c +++ b/src/startup.c @@ -17,6 +17,7 @@ #include #include +#include #define SN_API_NOT_YET_FROZEN 1 #include @@ -191,15 +192,7 @@ void start_application(const char *command, bool no_startup_id) { if (!no_startup_id) sn_launcher_context_setup_child_process(context); - /* Stores the path of the shell */ - static const char *shell = NULL; - - if (shell == NULL) - if ((shell = getenv("SHELL")) == NULL) - shell = "/bin/sh"; - - /* This is the child */ - execl(shell, shell, "-c", command, (void*)NULL); + execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (void*)NULL); /* not reached */ } _exit(0); From 9ee26a608e329ceea583950fcc92bc5a28f6cf55 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Thu, 31 Oct 2013 20:36:31 -0400 Subject: [PATCH 43/67] Return DRAG_ABORT on UnmapNotify from drag_pointer Add DRAG_ABORT to enum drag_result_t. DRAG_ABORT will indicate the drag operation cannot be completed. Return DRAG_ABORT on UnmapNotify, or when the keyboard or pointer cannot be grabbed. Add DRAGGING to return value for drag_result_t. DRAGGING is used internally by drag_pointer to indicate the drag is in progress. Change DRAG_CANCEL to DRAG_REVERT to clarify the distinction between "abort" and "revert/cancel" actions. Fixes an issue that caused i3 to crash when a user is dragging or resizing a floating window that becomes destroyed. --- include/floating.h | 23 +++++++++++++++++++---- src/floating.c | 31 ++++++++++++++----------------- src/resize.c | 2 +- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/include/floating.h b/include/floating.h index 04db1589..43600187 100644 --- a/include/floating.h +++ b/include/floating.h @@ -134,12 +134,27 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); #endif /** - * This is the return value of a drag operation like drag_pointer. DRAG_CANCEL - * will indicate the intention of the drag should not be carried out, or that - * the drag actions should be undone. + * 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 { DRAG_SUCCESS = 0, DRAG_CANCEL } drag_result_t; +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 diff --git a/src/floating.c b/src/floating.c index ae1a9192..9b9d3af7 100644 --- a/src/floating.c +++ b/src/floating.c @@ -441,14 +441,14 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { * after the user releases the mouse button */ tree_render(); - /* Store the initial rect in case of user cancel */ + /* Store the initial rect in case of user revert/cancel */ 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); /* If the user cancelled, undo the changes. */ - if (drag_result == DRAG_CANCEL) + if (drag_result == DRAG_REVERT) floating_reposition(con, initial_rect); /* If this is a scratchpad window, don't auto center it from now on. */ @@ -553,13 +553,13 @@ void floating_resize_window(Con *con, const bool proportional, struct resize_window_callback_params params = { corner, proportional, event }; - /* get the initial rect in case of cancel */ + /* 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); /* If the user cancels, undo the resize */ - if (drag_result == DRAG_CANCEL) + if (drag_result == DRAG_REVERT) floating_reposition(con, initial_rect); /* If this is a scratchpad window, don't auto center it from now on. */ @@ -601,7 +601,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) { ELOG("Could not grab pointer\n"); - return DRAG_CANCEL; + return DRAG_ABORT; } free(reply); @@ -620,7 +620,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) { ELOG("Could not grab keyboard\n"); - return DRAG_CANCEL; + return DRAG_ABORT; } free(keyb_reply); @@ -629,11 +629,9 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ xcb_flush(conn); xcb_generic_event_t *inside_event, *last_motion_notify = NULL; - bool loop_done = false; - /* The return value, set to DRAG_CANCEL on user cancel */ - drag_result_t drag_result = DRAG_SUCCESS; + drag_result_t drag_result = DRAGGING; /* I’ve always wanted to have my own eventhandler… */ - while (!loop_done && (inside_event = xcb_wait_for_event(conn))) { + while (drag_result == DRAGGING && (inside_event = xcb_wait_for_event(conn))) { /* We now handle all events we can get using xcb_poll_for_event */ do { /* skip x11 errors */ @@ -646,7 +644,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ switch (type) { case XCB_BUTTON_RELEASE: - loop_done = true; + drag_result = DRAG_SUCCESS; break; case XCB_MOTION_NOTIFY: @@ -657,16 +655,15 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ case XCB_UNMAP_NOTIFY: DLOG("Unmap-notify, aborting\n"); + drag_result = DRAG_ABORT; + handle_event(type, inside_event); - loop_done = true; - drag_result = DRAG_CANCEL; break; case XCB_KEY_PRESS: /* Cancel the drag if a key was pressed */ - DLOG("A key was pressed during drag, canceling."); - loop_done = true; - drag_result = DRAG_CANCEL; + DLOG("A key was pressed during drag, reverting changes."); + drag_result = DRAG_REVERT; handle_event(type, inside_event); break; @@ -681,7 +678,7 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ free(inside_event); } while ((inside_event = xcb_poll_for_event(conn)) != NULL); - if (last_motion_notify == NULL || loop_done) + if (last_motion_notify == NULL || drag_result != DRAGGING) continue; new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; diff --git a/src/resize.c b/src/resize.c index 764a485c..cc4ba841 100644 --- a/src/resize.c +++ b/src/resize.c @@ -161,7 +161,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, xcb_flush(conn); /* User cancelled the drag so no action should be taken. */ - if (drag_result == DRAG_CANCEL) + if (drag_result == DRAG_REVERT) return 0; int pixels; From e08215896e27ef3501e13501814214d933043e5b Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Wed, 30 Oct 2013 00:16:13 -0400 Subject: [PATCH 44/67] i3bar: Simplify hide mode logic When determining whether to hide or unhide the bar on redraw in hide mode, use simpler rules. When the config specifies the 'show' state or a workspace is urgent, or if the caller requests it, or the modifier is pressed, show the bar. Otherwise, hide the bar. --- i3bar/src/xcb.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 45648633..cec7ab82 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1626,9 +1626,6 @@ void draw_bars(bool unhide) { refresh_statusline(); - static char *last_urgent_ws = NULL; - bool walks_away = true; - i3_output *outputs_walk; SLIST_FOREACH(outputs_walk, outputs, slist) { if (!outputs_walk->active) { @@ -1697,9 +1694,6 @@ void draw_bars(bool unhide) { fg_color = colors.focus_ws_fg; bg_color = colors.focus_ws_bg; border_color = colors.focus_ws_border; - if (last_urgent_ws && strcmp(i3string_as_utf8(ws_walk->name), - last_urgent_ws) == 0) - walks_away = false; } } if (ws_walk->urgent) { @@ -1708,10 +1702,6 @@ void draw_bars(bool unhide) { bg_color = colors.urgent_ws_bg; border_color = colors.urgent_ws_border; unhide = true; - if (!ws_walk->focused) { - FREE(last_urgent_ws); - last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name)); - } } uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; uint32_t vals_border[] = { border_color, border_color }; @@ -1783,13 +1773,11 @@ void draw_bars(bool unhide) { } /* Assure the bar is hidden/unhidden according to the specified hidden_state and mode */ - bool should_unhide = (config.hidden_state == S_SHOW || (unhide && config.hidden_state == S_HIDE)); - bool should_hide = (config.hide_on_modifier == M_INVISIBLE); - - if (mod_pressed || (should_unhide && !should_hide)) { + if (mod_pressed || + config.hidden_state == S_SHOW || + unhide) { unhide_bars(); - } else if (!mod_pressed && (walks_away || should_hide)) { - FREE(last_urgent_ws); + } else if (config.hide_on_modifier == M_HIDE) { hide_bars(); } From f3bf314662e739e4c0ab3264e793871983242325 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sat, 9 Nov 2013 12:51:44 -0500 Subject: [PATCH 45/67] Add sensible discretion to UnmapNotify drag abort Only abort a drag action on UnmapNotify when the unmapping window is managed on the current workspace. fixes #1108 --- src/floating.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/floating.c b/src/floating.c index 9b9d3af7..10962403 100644 --- a/src/floating.c +++ b/src/floating.c @@ -629,6 +629,8 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ xcb_flush(conn); xcb_generic_event_t *inside_event, *last_motion_notify = NULL; + Con *inside_con = NULL; + drag_result_t drag_result = DRAGGING; /* I’ve always wanted to have my own eventhandler… */ while (drag_result == DRAGGING && (inside_event = xcb_wait_for_event(conn))) { @@ -654,8 +656,16 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_ break; case XCB_UNMAP_NOTIFY: - DLOG("Unmap-notify, aborting\n"); - drag_result = DRAG_ABORT; + inside_con = con_by_window_id(((xcb_unmap_notify_event_t*)inside_event)->window); + + if (inside_con != NULL) { + DLOG("UnmapNotify for window 0x%08x (container %p)\n", ((xcb_unmap_notify_event_t*)inside_event)->window, inside_con); + + if (con_get_workspace(inside_con) == con_get_workspace(focused)) { + DLOG("UnmapNotify for a managed window on the current workspace, aborting\n"); + drag_result = DRAG_ABORT; + } + } handle_event(type, inside_event); break; From 3dba51500ede957394f0dafa4bd71c9123ee05f1 Mon Sep 17 00:00:00 2001 From: Trung Ngo Date: Sun, 10 Nov 2013 16:10:17 +0700 Subject: [PATCH 46/67] Update userguide on multiple criteria Change wording, add an example for multiple criteria and move the sentence explaining the Firefox example into the code listing block. --- docs/userguide | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/userguide b/docs/userguide index a0862e64..db256cc4 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1285,16 +1285,20 @@ bindsym $mod+x move container to workspace 3; workspace 3 [[command_criteria]] Furthermore, you can change the scope of a command - that is, which containers -should be affected by that command, by using various criteria. These are -prefixed in square brackets to every command. If you want to kill all windows -which have the class Firefox, use: +should be affected by that command, by using various criteria. The criteria +are specified before any command in a pair of square brackets and are separated +by space. *Example*: ------------------------------------ +# if you want to kill all windows which have the class Firefox, use: bindsym $mod+x [class="Firefox"] kill # same thing, but case-insensitive bindsym $mod+x [class="(?i)firefox"] kill + +# kill only the About dialog from Firefox +bindsym $mod+x [class="Firefox" window_role="About"] kill ------------------------------------ The criteria which are currently implemented are: From 1d6450f0e8b64e17cc5996bf9cf5e6dc07cf54a9 Mon Sep 17 00:00:00 2001 From: Bas Pape Date: Wed, 13 Nov 2013 20:23:35 +0100 Subject: [PATCH 47/67] libi3/font: Set DPI for the pango context The pango font specification accepts a font size in points, but pango defaults to a DPI of 96. Create a default PangoContext (which internally creates a default PangoCairoFontMap as usual) and set the DPI to the value of the root Screen manually. Fixes #1115 --- libi3/font.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/libi3/font.c b/libi3/font.c index c57009c0..9cea83e4 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -56,7 +56,10 @@ static bool load_pango_font(i3Font *font, const char *desc) { /* Create a dummy Pango layout to compute the font height */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); cairo_t *cr = cairo_create(surface); - PangoLayout *layout = pango_cairo_create_layout(cr); + double ydpi = (double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters; + PangoContext *pc = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(pc, ydpi); + PangoLayout *layout = pango_layout_new(pc); pango_layout_set_font_description(layout, font->specific.pango_desc); /* Get the font height */ @@ -66,6 +69,7 @@ static bool load_pango_font(i3Font *font, const char *desc) { /* Free resources */ g_object_unref(layout); + g_object_unref(pc); cairo_destroy(cr); cairo_surface_destroy(surface); @@ -85,7 +89,10 @@ static void draw_text_pango(const char *text, size_t text_len, cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable, root_visual_type, x + max_width, y + savedFont->height); cairo_t *cr = cairo_create(surface); - PangoLayout *layout = pango_cairo_create_layout(cr); + double ydpi = (double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters; + PangoContext *pc = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(pc, ydpi); + PangoLayout *layout = pango_layout_new(pc); gint height; pango_layout_set_font_description(layout, savedFont->specific.pango_desc); @@ -104,6 +111,7 @@ static void draw_text_pango(const char *text, size_t text_len, /* Free resources */ g_object_unref(layout); + g_object_unref(pc); cairo_destroy(cr); cairo_surface_destroy(surface); } @@ -117,7 +125,10 @@ static int predict_text_width_pango(const char *text, size_t text_len) { /* root_visual_type is cached in load_pango_font */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); cairo_t *cr = cairo_create(surface); - PangoLayout *layout = pango_cairo_create_layout(cr); + double ydpi = (double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters; + PangoContext *pc = pango_cairo_create_context(cr); + pango_cairo_context_set_resolution(pc, ydpi); + PangoLayout *layout = pango_layout_new(pc); /* Get the font width */ gint width; @@ -128,6 +139,7 @@ static int predict_text_width_pango(const char *text, size_t text_len) { /* Free resources */ g_object_unref(layout); + g_object_unref(pc); cairo_destroy(cr); cairo_surface_destroy(surface); From 5a4efd04c1892e8d06dc695db55b24fdbd4fd0b6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 18 Nov 2013 23:17:44 +0100 Subject: [PATCH 48/67] =?UTF-8?q?refactor=20previous=20commit=E2=80=99s=20?= =?UTF-8?q?new=20code=20into=20a=20function,=20add=20log=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libi3/font.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/libi3/font.c b/libi3/font.c index 9cea83e4..4c064f2b 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -30,6 +30,21 @@ static double pango_font_red; static double pango_font_green; static double pango_font_blue; +static PangoLayout *create_layout_with_dpi(cairo_t *cr) { + PangoLayout *layout; + PangoContext *context; + + context = pango_cairo_create_context(cr); + const double dpi = (double)root_screen->height_in_pixels * 25.4 / + (double)root_screen->height_in_millimeters; + LOG("X11 root window dictates %f DPI\n", dpi); + pango_cairo_context_set_resolution(context, dpi); + layout = pango_layout_new(context); + g_object_unref(context); + + return layout; +} + /* * Loads a Pango font description into an i3Font structure. Returns true * on success, false otherwise. @@ -56,10 +71,7 @@ static bool load_pango_font(i3Font *font, const char *desc) { /* Create a dummy Pango layout to compute the font height */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); cairo_t *cr = cairo_create(surface); - double ydpi = (double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters; - PangoContext *pc = pango_cairo_create_context(cr); - pango_cairo_context_set_resolution(pc, ydpi); - PangoLayout *layout = pango_layout_new(pc); + PangoLayout *layout = create_layout_with_dpi(cr); pango_layout_set_font_description(layout, font->specific.pango_desc); /* Get the font height */ @@ -69,7 +81,6 @@ static bool load_pango_font(i3Font *font, const char *desc) { /* Free resources */ g_object_unref(layout); - g_object_unref(pc); cairo_destroy(cr); cairo_surface_destroy(surface); @@ -89,10 +100,7 @@ static void draw_text_pango(const char *text, size_t text_len, cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable, root_visual_type, x + max_width, y + savedFont->height); cairo_t *cr = cairo_create(surface); - double ydpi = (double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters; - PangoContext *pc = pango_cairo_create_context(cr); - pango_cairo_context_set_resolution(pc, ydpi); - PangoLayout *layout = pango_layout_new(pc); + PangoLayout *layout = create_layout_with_dpi(cr); gint height; pango_layout_set_font_description(layout, savedFont->specific.pango_desc); @@ -111,7 +119,6 @@ static void draw_text_pango(const char *text, size_t text_len, /* Free resources */ g_object_unref(layout); - g_object_unref(pc); cairo_destroy(cr); cairo_surface_destroy(surface); } @@ -125,10 +132,7 @@ static int predict_text_width_pango(const char *text, size_t text_len) { /* root_visual_type is cached in load_pango_font */ cairo_surface_t *surface = cairo_xcb_surface_create(conn, root_screen->root, root_visual_type, 1, 1); cairo_t *cr = cairo_create(surface); - double ydpi = (double)root_screen->height_in_pixels * 25.4 / (double)root_screen->height_in_millimeters; - PangoContext *pc = pango_cairo_create_context(cr); - pango_cairo_context_set_resolution(pc, ydpi); - PangoLayout *layout = pango_layout_new(pc); + PangoLayout *layout = create_layout_with_dpi(cr); /* Get the font width */ gint width; @@ -139,7 +143,6 @@ static int predict_text_width_pango(const char *text, size_t text_len) { /* Free resources */ g_object_unref(layout); - g_object_unref(pc); cairo_destroy(cr); cairo_surface_destroy(surface); From 9f955a1e012f5e6c3bf5428924c778e9cac346e0 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Wed, 13 Nov 2013 03:39:32 -0500 Subject: [PATCH 49/67] Command 'move ' moves across outputs When 'move ' is issued in the context of a container that borders a workspace, and there is no suitable place within this workspace for which this container can move, move the container to the closest output in this direction instead. --- src/move.c | 64 +++++++++++++++++++------ testcases/t/516-move.t | 106 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 testcases/t/516-move.t diff --git a/src/move.c b/src/move.c index 46b90177..0b3ab660 100644 --- a/src/move.c +++ b/src/move.c @@ -65,11 +65,12 @@ static void insert_con_into(Con *con, Con *target, position_t position) { } /* - * This function detaches 'con' from its parent and inserts it at the given - * workspace. + * This function detaches 'con' from its parent and puts it in the given + * workspace. Position is determined by the direction of movement into the + * workspace container. * */ -static void attach_to_workspace(Con *con, Con *ws) { +static void attach_to_workspace(Con *con, Con *ws, direction_t direction) { con_detach(con); con_fix_percent(con->parent); @@ -77,8 +78,13 @@ static void attach_to_workspace(Con *con, Con *ws) { con->parent = ws; - TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes); - TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused); + if (direction == D_RIGHT || direction == D_DOWN) { + TAILQ_INSERT_HEAD(&(ws->nodes_head), con, nodes); + TAILQ_INSERT_HEAD(&(ws->focus_head), con, focused); + } else { + TAILQ_INSERT_TAIL(&(ws->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(ws->focus_head), con, focused); + } /* Pretend the con was just opened with regards to size percent values. * Since the con is moved to a completely different con, the old value @@ -87,6 +93,32 @@ static void attach_to_workspace(Con *con, Con *ws) { con_fix_percent(ws); } +/* + * Moves the given container to the closest output in the given direction if + * such an output exists. + * + */ +static void move_to_output_directed(Con *con, direction_t direction) { + Con *current_output_con = con_get_output(con); + Output *current_output = get_output_by_name(current_output_con->name); + Output *output = get_output_next(direction, current_output, CLOSEST_OUTPUT); + + if (!output) { + DLOG("No output in this direction found. Not moving.\n"); + return; + } + + Con *ws = NULL; + GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); + + if (!ws) { + DLOG("No workspace on output in this direction found. Not moving.\n"); + return; + } + + attach_to_workspace(con, ws, direction); +} + /* * Moves the current container in the given direction (D_LEFT, D_RIGHT, * D_UP, D_DOWN). @@ -103,8 +135,9 @@ void tree_move(int direction) { } if (con->parent->type == CT_WORKSPACE && con_num_children(con->parent) == 1) { - DLOG("This is the only con on this workspace, not doing anything\n"); - return; + /* This is the only con on this workspace */ + move_to_output_directed(con, direction); + goto end; } orientation_t o = (direction == D_LEFT || direction == D_RIGHT ? HORIZ : VERT); @@ -124,7 +157,7 @@ void tree_move(int direction) { if (con_inside_floating(con)) { /* 'con' should be moved out of a floating container */ DLOG("Inside floating, moving to workspace\n"); - attach_to_workspace(con, con_get_workspace(con)); + attach_to_workspace(con, con_get_workspace(con), direction); goto end; } DLOG("Force-changing orientation\n"); @@ -154,12 +187,15 @@ void tree_move(int direction) { return; } - /* If there was no con with which we could swap the current one, search - * again, but starting one level higher. If we are on the workspace - * level, don’t do that. The result would be a force change of - * workspace orientation, which is not necessary. */ - if (con->parent == con_get_workspace(con)) - return; + if (con->parent == con_get_workspace(con)) { + /* If we couldn't find a place to move it on this workspace, + * try to move it to a workspace on a different output */ + move_to_output_directed(con, direction); + goto end; + } + + /* If there was no con with which we could swap the current one, + * search again, but starting one level higher. */ same_orientation = con_parent_with_orientation(con->parent, o); } } while (same_orientation == NULL); diff --git a/testcases/t/516-move.t b/testcases/t/516-move.t new file mode 100644 index 00000000..512e20b9 --- /dev/null +++ b/testcases/t/516-move.t @@ -0,0 +1,106 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests if a simple 'move ' command will move containers across outputs. +# +use i3test i3_autostart => 0; + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = <unmap; +wait_for_unmap; + +##################################################################### +# Try to move a window on a workspace with two windows across outputs in each +# direction +##################################################################### + +# from left-top to right-top +cmd('workspace left-top'); +cmd('split h'); +my $first_window = open_window; +my $social_window = open_window( name => 'CORRECT_WINDOW' ); +cmd('move right'); +is(scalar @{get_ws_content('right-top')}, 1, 'moved some window to right-top workspace'); +my $compare_window = shift @{get_ws_content('right-top')}; +is($compare_window->{name}, $social_window->name, 'moved correct window to right-top workspace'); +# unamp the first window so we don't confuse it when we move back here +$first_window->unmap; +wait_for_unmap; + +# from right-top to right-bottom +cmd('split v'); +open_window; +# this window opened above - we need to move down twice +cmd('focus up; move down; move down'); +is(scalar @{get_ws_content('right-bottom')}, 1, 'moved some window to right-bottom workspace'); +$compare_window = shift @{get_ws_content('right-bottom')}; +is($compare_window->{name}, $social_window->name, 'moved correct window to right-bottom workspace'); + +# from right-bottom to left-bottom +cmd('split h'); +open_window; +cmd('focus left; move left'); +is(scalar @{get_ws_content('left-bottom')}, 1, 'moved some window to left-bottom workspace'); +$compare_window = shift @{get_ws_content('left-bottom')}; +is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace'); + +# from left-bottom to left-top +cmd('split v'); +open_window; +cmd('focus up; move up'); +is(scalar @{get_ws_content('left-top')}, 1, 'moved some window to left-bottom workspace'); +$compare_window = shift @{get_ws_content('left-top')}; +is($social_window->name, $compare_window->{name}, 'moved correct window to left-bottom workspace'); + +exit_gracefully($pid); + +done_testing; From ca39289e3e333f1abe63c155e6516375920a0df2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 19 Nov 2013 20:28:32 +0100 Subject: [PATCH 50/67] t/516-move: use window id, fix typo --- testcases/t/516-move.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/t/516-move.t b/testcases/t/516-move.t index 512e20b9..0d21ca31 100644 --- a/testcases/t/516-move.t +++ b/testcases/t/516-move.t @@ -71,8 +71,8 @@ my $social_window = open_window( name => 'CORRECT_WINDOW' ); cmd('move right'); is(scalar @{get_ws_content('right-top')}, 1, 'moved some window to right-top workspace'); my $compare_window = shift @{get_ws_content('right-top')}; -is($compare_window->{name}, $social_window->name, 'moved correct window to right-top workspace'); -# unamp the first window so we don't confuse it when we move back here +is($compare_window->{window}, $social_window->id, 'moved correct window to right-top workspace'); +# unmap the first window so we don't confuse it when we move back here $first_window->unmap; wait_for_unmap; From d3beff23395356ec71fcc35a3b802163e12df73e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 22 Nov 2013 15:48:45 +0100 Subject: [PATCH 51/67] =?UTF-8?q?make=20i3bar=20use=20libi3=E2=80=99s=20ro?= =?UTF-8?q?ot=5Fatom=5Fcontents()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes code duplication, which will be useful for a subsequent commit. Furthermore, we now don’t open X11 connections unnecessarily in some corner cases. --- i3-config-wizard/main.c | 14 +++++++------- i3-dump-log/main.c | 4 ++-- i3-input/main.c | 14 +++++++------- i3-msg/main.c | 2 +- i3bar/src/xcb.c | 21 +-------------------- include/libi3.h | 5 ++++- libi3/root_atom_contents.c | 16 ++++++++++------ src/display_version.c | 4 ++-- src/main.c | 4 ++-- 9 files changed, 36 insertions(+), 48 deletions(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index ffc3df93..880b80ed 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -791,17 +791,17 @@ int main(int argc, char *argv[]) { close(fd); unlink(config_path); + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + errx(1, "Cannot open display\n"); + if (socket_path == NULL) - socket_path = root_atom_contents("I3_SOCKET_PATH"); + socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen); if (socket_path == NULL) socket_path = "/tmp/i3-ipc.sock"; - int screens; - if ((conn = xcb_connect(NULL, &screens)) == NULL || - xcb_connection_has_error(conn)) - errx(1, "Cannot open display\n"); - keysyms = xcb_key_symbols_alloc(conn); xcb_get_modifier_mapping_cookie_t modmap_cookie; modmap_cookie = xcb_get_modifier_mapping(conn); @@ -813,7 +813,7 @@ int main(int argc, char *argv[]) { #include "atoms.xmacro" #undef xmacro - root_screen = xcb_aux_get_screen(conn, screens); + root_screen = xcb_aux_get_screen(conn, screen); root = root_screen->root; if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL))) diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c index 852a5cf7..acb7fcb1 100644 --- a/i3-dump-log/main.c +++ b/i3-dump-log/main.c @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) { } } - char *shmname = root_atom_contents("I3_SHMLOG_PATH"); + char *shmname = root_atom_contents("I3_SHMLOG_PATH", NULL, 0); if (shmname == NULL) { /* Something failed. Let’s invest a little effort to find out what it * is. This is hugely helpful for users who want to debug i3 but are @@ -109,7 +109,7 @@ int main(int argc, char *argv[]) { fprintf(stderr, "FYI: The DISPLAY environment variable is set to \"%s\".\n", getenv("DISPLAY")); exit(1); } - if (root_atom_contents("I3_CONFIG_PATH") != NULL) { + if (root_atom_contents("I3_CONFIG_PATH", conn, screen) != NULL) { fprintf(stderr, "i3-dump-log: ERROR: i3 is running, but SHM logging is not enabled.\n\n"); if (!is_debug_build()) { fprintf(stderr, "You seem to be using a release version of i3:\n %s\n\n", I3_VERSION); diff --git a/i3-input/main.c b/i3-input/main.c index 49db4df2..da95c903 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -368,23 +368,23 @@ int main(int argc, char *argv[]) { printf("using format \"%s\"\n", format); + int screen; + conn = xcb_connect(NULL, &screen); + if (!conn || xcb_connection_has_error(conn)) + die("Cannot open display\n"); + if (socket_path == NULL) - socket_path = root_atom_contents("I3_SOCKET_PATH"); + socket_path = root_atom_contents("I3_SOCKET_PATH", conn, screen); if (socket_path == NULL) socket_path = "/tmp/i3-ipc.sock"; sockfd = ipc_connect(socket_path); - int screens; - conn = xcb_connect(NULL, &screens); - if (!conn || xcb_connection_has_error(conn)) - die("Cannot open display\n"); - /* Request the current InputFocus to restore when i3-input exits. */ focus_cookie = xcb_get_input_focus(conn); - root_screen = xcb_aux_get_screen(conn, screens); + root_screen = xcb_aux_get_screen(conn, screen); root = root_screen->root; symbols = xcb_key_symbols_alloc(conn); diff --git a/i3-msg/main.c b/i3-msg/main.c index a1428fb8..935edc04 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -188,7 +188,7 @@ int main(int argc, char *argv[]) { } if (socket_path == NULL) - socket_path = root_atom_contents("I3_SOCKET_PATH"); + socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0); /* Fall back to the default socket path */ if (socket_path == NULL) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index cec7ab82..0c8de65e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -960,26 +960,7 @@ char *init_xcb_early() { /* Now we get the atoms and save them in a nice data structure */ get_atoms(); - xcb_get_property_cookie_t path_cookie; - path_cookie = xcb_get_property_unchecked(xcb_connection, - 0, - xcb_root, - atoms[I3_SOCKET_PATH], - XCB_GET_PROPERTY_TYPE_ANY, - 0, PATH_MAX); - - /* We check, if i3 set its socket-path */ - xcb_get_property_reply_t *path_reply = xcb_get_property_reply(xcb_connection, - path_cookie, - NULL); - char *path = NULL; - if (path_reply) { - int len = xcb_get_property_value_length(path_reply); - if (len != 0) { - path = strndup(xcb_get_property_value(path_reply), len); - } - } - + char *path = root_atom_contents("I3_SOCKET_PATH", xcb_connection, screen); if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") || xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") || diff --git a/include/libi3.h b/include/libi3.h index b0141f1d..9ba78004 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -84,11 +84,14 @@ void errorlog(char *fmt, ...); * Try to get the contents of the given atom (for example I3_SOCKET_PATH) from * the X11 root window and return NULL if it doesn’t work. * + * If the provided XCB connection is NULL, a new connection will be + * established. + * * The memory for the contents is dynamically allocated and has to be * free()d by the caller. * */ -char *root_atom_contents(const char *atomname); +char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen); /** * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that diff --git a/libi3/root_atom_contents.c b/libi3/root_atom_contents.c index cabaaf2c..697441eb 100644 --- a/libi3/root_atom_contents.c +++ b/libi3/root_atom_contents.c @@ -19,19 +19,22 @@ * Try to get the contents of the given atom (for example I3_SOCKET_PATH) from * the X11 root window and return NULL if it doesn’t work. * + * If the provided XCB connection is NULL, a new connection will be + * established. + * * The memory for the contents is dynamically allocated and has to be * free()d by the caller. * */ -char *root_atom_contents(const char *atomname) { - xcb_connection_t *conn; +char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, int screen) { xcb_intern_atom_cookie_t atom_cookie; xcb_intern_atom_reply_t *atom_reply; - int screen; char *content; + xcb_connection_t *conn = provided_conn; - if ((conn = xcb_connect(NULL, &screen)) == NULL || - xcb_connection_has_error(conn)) + if (provided_conn == NULL && + ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn))) return NULL; atom_cookie = xcb_intern_atom(conn, 0, strlen(atomname), atomname); @@ -60,7 +63,8 @@ char *root_atom_contents(const char *atomname) { (char*)xcb_get_property_value(prop_reply)) == -1) return NULL; } - xcb_disconnect(conn); + if (provided_conn == NULL) + xcb_disconnect(conn); return content; } diff --git a/src/display_version.c b/src/display_version.c index 0901ae07..5fa4f8e2 100644 --- a/src/display_version.c +++ b/src/display_version.c @@ -68,11 +68,11 @@ static yajl_callbacks version_callbacks = { * */ void display_running_version(void) { - char *socket_path = root_atom_contents("I3_SOCKET_PATH"); + char *socket_path = root_atom_contents("I3_SOCKET_PATH", conn, conn_screen); if (socket_path == NULL) exit(EXIT_SUCCESS); - char *pid_from_atom = root_atom_contents("I3_PID"); + char *pid_from_atom = root_atom_contents("I3_PID", conn, conn_screen); if (pid_from_atom == NULL) { /* If I3_PID is not set, the running version is older than 4.2-200. */ printf("\nRunning version: < 4.2-200\n"); diff --git a/src/main.c b/src/main.c index aac73883..2b397181 100644 --- a/src/main.c +++ b/src/main.c @@ -352,7 +352,7 @@ int main(int argc, char *argv[]) { break; } else if (strcmp(long_options[option_index].name, "get-socketpath") == 0 || strcmp(long_options[option_index].name, "get_socketpath") == 0) { - char *socket_path = root_atom_contents("I3_SOCKET_PATH"); + char *socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0); if (socket_path) { printf("%s\n", socket_path); exit(EXIT_SUCCESS); @@ -442,7 +442,7 @@ int main(int argc, char *argv[]) { optind++; } DLOG("Command is: %s (%zd bytes)\n", payload, strlen(payload)); - char *socket_path = root_atom_contents("I3_SOCKET_PATH"); + char *socket_path = root_atom_contents("I3_SOCKET_PATH", NULL, 0); if (!socket_path) { ELOG("Could not get i3 IPC socket path\n"); return 1; From a1e7ce20f0e9dd072610ac549561c28fba2fd55b Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Fri, 22 Nov 2013 21:22:56 -0500 Subject: [PATCH 52/67] i3bar: Handle DestroyNotify events Handle DestroyNotify events by removing the tray client from the tray client list held in memory. This change is intended to be part of the i3bar's implementation of the XEmbed protocol. For more information, see: According to the XEmbed protocol specification, this is one way for a tray client to finish the protocol. After this event is received, i3bar should have no more interaction with the tray client. --- i3bar/src/xcb.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 0c8de65e..f25888b0 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -622,6 +622,39 @@ static void handle_client_message(xcb_client_message_event_t* event) { } } +/* + * Handles DestroyNotify events by removing the tray client from the data + * structure. According to the XEmbed protocol, this is one way for a tray + * client to finish the protocol. After this event is received, there is no + * further interaction with the tray client. + * + * See: http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + * + */ +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); + + /* Trigger an update, we now have more space for the statusline */ + configure_trayclients(); + draw_bars(false); + return; + } + } +} + /* * Handles UnmapNotify events. These events happen when a tray window unmaps * itself. We then update our data structure @@ -798,8 +831,11 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { * example system tray widgets talk to us directly via client messages. */ handle_client_message((xcb_client_message_event_t*) event); break; - case XCB_UNMAP_NOTIFY: case XCB_DESTROY_NOTIFY: + /* DestroyNotify signifies the end of the XEmbed protocol */ + handle_destroy_notify((xcb_destroy_notify_event_t*) event); + break; + case XCB_UNMAP_NOTIFY: /* UnmapNotifies are received when a tray window unmaps itself */ handle_unmap_notify((xcb_unmap_notify_event_t*) event); break; From 4f5e0e794f61cdcea2652f964ec25d0b7418f69b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 24 Nov 2013 13:44:30 +0100 Subject: [PATCH 53/67] =?UTF-8?q?persist=20root=20window=E2=80=99s=20backg?= =?UTF-8?q?round=20contents=20to=20a=20pixmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit title is fairly technical, so I’ll try to explain. Recently, users of GDM3 (I’m sure) and LightDM (I think) have reported that when switching to a new workspace, the contents of the previous workspace are still visible. i3bar updates, though, so it is the X11 root window which is not being updated here. When using GDM3, X11 will be started with -background none, and no background pixmap or pixel is set. Then, apparently, gnome-settings-daemon will display a fade animation from whatever is currently on the window to the destination contents. I think this is to avoid flickering when logging in, which would occur when just setting a specific background pixmap or pixel. So, this commit will, when i3 starts first (not on restarts), copy the contents of the X11 root window (typicall a grey background, at least on my machine with GDM3) into a pixmap and set that pixmap as background pixmap. That way, the content will be preserved and one has a background, instead of what is perceived as a bug :). This commit has some chance of breakage, so I’m prepared to revert it unless we can figure out the issues and roll forward. --- src/main.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main.c b/src/main.c index 2b397181..a20fe31b 100644 --- a/src/main.c +++ b/src/main.c @@ -794,6 +794,27 @@ int main(int argc, char *argv[]) { } xcb_ungrab_server(conn); + if (autostart) { + LOG("This is not an in-place restart, copying root window contents to a pixmap\n"); + xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen); + uint16_t width = root->width_in_pixels; + uint16_t height = root->height_in_pixels; + xcb_pixmap_t pixmap = xcb_generate_id(conn); + xcb_gcontext_t gc = xcb_generate_id(conn); + + xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height); + + xcb_create_gc(conn, gc, root->root, + XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE, + (uint32_t[]){ XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS }); + + xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height); + xcb_change_window_attributes_checked(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){ pixmap }); + xcb_flush(conn); + xcb_free_gc(conn, gc); + xcb_free_pixmap(conn, pixmap); + } + struct sigaction action; action.sa_sigaction = handle_signal; From f22995393ab5d05f3d17f682064154fd264ff495 Mon Sep 17 00:00:00 2001 From: Lancelot SIX Date: Thu, 21 Nov 2013 22:03:49 +0100 Subject: [PATCH 54/67] Remove references to PATH_MAX macro Since the macro PATH_MAX is not defined on every system (GNU/Hurd being one of those who do not define it), we remove all references to this macro. Instead, we use a buffer of arbitraty size and grow it when needed to contain paths. --- i3-nagbar/main.c | 4 +++- include/libi3.h | 3 ++- libi3/get_exe_path.c | 55 ++++++++++++++++++++++++++++++++----------- src/display_version.c | 28 ++++++++++++++++------ src/main.c | 15 ++++++++---- 5 files changed, 78 insertions(+), 27 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 0fa75f8e..952270e7 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -159,8 +159,9 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve fclose(script); char *link_path; + char *exe_path = get_exe_path(argv0); sasprintf(&link_path, "%s.nagbar_cmd", script_path); - symlink(get_exe_path(argv0), link_path); + symlink(exe_path, link_path); char *terminal_cmd; sasprintf(&terminal_cmd, "i3-sensible-terminal -e %s", link_path); @@ -172,6 +173,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve free(link_path); free(terminal_cmd); free(script_path); + free(exe_path); /* TODO: unset flag, re-render */ } diff --git a/include/libi3.h b/include/libi3.h index 9ba78004..18c64690 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -372,7 +372,8 @@ char *get_process_filename(const char *prefix); * * The implementation follows http://stackoverflow.com/a/933996/712014 * + * Returned value must be freed by the caller. */ -const char *get_exe_path(const char *argv0); +char *get_exe_path(const char *argv0); #endif diff --git a/libi3/get_exe_path.c b/libi3/get_exe_path.c index fca7ba02..e0437e5e 100644 --- a/libi3/get_exe_path.c +++ b/libi3/get_exe_path.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "libi3.h" @@ -11,10 +12,14 @@ * * The implementation follows http://stackoverflow.com/a/933996/712014 * + * Returned value must be freed by the caller. */ -const char *get_exe_path(const char *argv0) { - static char destpath[PATH_MAX]; - char tmp[PATH_MAX]; +char *get_exe_path(const char *argv0) { + size_t destpath_size = 1024; + size_t tmp_size = 1024; + char *destpath = smalloc(destpath_size); + char *tmp = smalloc(tmp_size); + #if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) /* Linux and Debian/kFreeBSD provide /proc/self/exe */ @@ -25,30 +30,48 @@ const char *get_exe_path(const char *argv0) { #endif ssize_t linksize; - if ((linksize = readlink(exepath, destpath, sizeof(destpath) - 1)) != -1) { + while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) { + destpath_size = destpath_size * 2; + destpath = srealloc(destpath, destpath_size); + } + if (linksize != -1) { /* readlink() does not NULL-terminate strings, so we have to. */ destpath[linksize] = '\0'; - + free(tmp); return destpath; } #endif /* argv[0] is most likely a full path if it starts with a slash. */ - if (argv0[0] == '/') - return argv0; + if (argv0[0] == '/') { + free(tmp); + free(destpath); + return sstrdup(argv0); + } /* if argv[0] contains a /, prepend the working directory */ - if (strchr(argv0, '/') != NULL && - getcwd(tmp, sizeof(tmp)) != NULL) { - snprintf(destpath, sizeof(destpath), "%s/%s", tmp, argv0); - return destpath; + if (strchr(argv0, '/') != NULL) { + char *retgcwd; + while ((retgcwd = getcwd(tmp, tmp_size)) == NULL && errno == ERANGE) { + tmp_size = tmp_size * 2; + tmp = srealloc(tmp, tmp_size); + } + if (retgcwd != NULL) { + free(destpath); + sasprintf(&destpath, "%s/%s", tmp, argv0); + free(tmp); + return destpath; + } } /* Fall back to searching $PATH (or _CS_PATH in absence of $PATH). */ char *path = getenv("PATH"); if (path == NULL) { /* _CS_PATH is typically something like "/bin:/usr/bin" */ - confstr(_CS_PATH, tmp, sizeof(tmp)); + while (confstr(_CS_PATH, tmp, tmp_size) > tmp_size) { + tmp_size = tmp_size * 2; + tmp = srealloc(tmp, tmp_size); + } sasprintf(&path, ":%s", tmp); } else { path = strdup(path); @@ -59,16 +82,20 @@ const char *get_exe_path(const char *argv0) { if ((component = strtok(str, ":")) == NULL) break; str = NULL; - snprintf(destpath, sizeof(destpath), "%s/%s", component, argv0); + free(destpath); + sasprintf(&destpath, "%s/%s", component, argv0); /* Of course this is not 100% equivalent to actually exec()ing the * binary, but meh. */ if (access(destpath, X_OK) == 0) { free(path); + free(tmp); return destpath; } } + free(destpath); free(path); + free(tmp); /* Last resort: maybe it’s in /usr/bin? */ - return "/usr/bin/i3-nagbar"; + return sstrdup("/usr/bin/i3-nagbar"); } diff --git a/src/display_version.c b/src/display_version.c index 5fa4f8e2..427c4ff7 100644 --- a/src/display_version.c +++ b/src/display_version.c @@ -128,13 +128,18 @@ void display_running_version(void) { printf("\rRunning i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom); #ifdef __linux__ - char exepath[PATH_MAX], - destpath[PATH_MAX]; + size_t destpath_size = 1024; ssize_t linksize; + char *exepath; + char *destpath = smalloc(destpath_size); - snprintf(exepath, sizeof(exepath), "/proc/%d/exe", getpid()); + sasprintf(&exepath, "/proc/%d/exe", getpid()); - if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1) + while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) { + destpath_size = destpath_size * 2; + destpath = srealloc(destpath, destpath_size); + } + if (linksize == -1) err(EXIT_FAILURE, "readlink(%s)", exepath); /* readlink() does not NULL-terminate strings, so we have to. */ @@ -143,9 +148,14 @@ void display_running_version(void) { printf("\n"); printf("The i3 binary you just called: %s\n", destpath); - snprintf(exepath, sizeof(exepath), "/proc/%s/exe", pid_from_atom); + free(exepath); + sasprintf(&exepath, "/proc/%s/exe", pid_from_atom); - if ((linksize = readlink(exepath, destpath, sizeof(destpath))) == -1) + while ((linksize = readlink(exepath, destpath, destpath_size)) == destpath_size) { + destpath_size = destpath_size * 2; + destpath = srealloc(destpath, destpath_size); + } + if (linksize == -1) err(EXIT_FAILURE, "readlink(%s)", exepath); /* readlink() does not NULL-terminate strings, so we have to. */ @@ -159,7 +169,8 @@ void display_running_version(void) { /* Since readlink() might put a "(deleted)" somewhere in the buffer and * stripping that out seems hackish and ugly, we read the process’s argv[0] * instead. */ - snprintf(exepath, sizeof(exepath), "/proc/%s/cmdline", pid_from_atom); + free(exepath); + sasprintf(&exepath, "/proc/%s/cmdline", pid_from_atom); int fd; if ((fd = open(exepath, O_RDONLY)) == -1) @@ -169,6 +180,9 @@ void display_running_version(void) { close(fd); printf("The i3 binary you are running: %s\n", destpath); + + free(exepath); + free(destpath); #endif yajl_free(handle); diff --git a/src/main.c b/src/main.c index a20fe31b..6028e1dd 100644 --- a/src/main.c +++ b/src/main.c @@ -488,18 +488,25 @@ int main(int argc, char *argv[]) { /* The following code is helpful, but not required. We thus don’t pay * much attention to error handling, non-linux or other edge cases. */ - char cwd[PATH_MAX]; LOG("CORE DUMPS: You are running a development version of i3, so coredumps were automatically enabled (ulimit -c unlimited).\n"); - if (getcwd(cwd, sizeof(cwd)) != NULL) + size_t cwd_size = 1024; + char *cwd = smalloc(cwd_size); + char *cwd_ret; + while ((cwd_ret = getcwd(cwd, cwd_size)) == NULL && errno == ERANGE) { + cwd_size = cwd_size * 2; + cwd = srealloc(cwd, cwd_size); + } + if (cwd_ret != NULL) LOG("CORE DUMPS: Your current working directory is \"%s\".\n", cwd); int patternfd; if ((patternfd = open("/proc/sys/kernel/core_pattern", O_RDONLY)) >= 0) { - memset(cwd, '\0', sizeof(cwd)); - if (read(patternfd, cwd, sizeof(cwd)) > 0) + memset(cwd, '\0', cwd_size); + if (read(patternfd, cwd, cwd_size) > 0) /* a trailing newline is included in cwd */ LOG("CORE DUMPS: Your core_pattern is: %s", cwd); close(patternfd); } + free(cwd); } LOG("i3 " I3_VERSION " starting\n"); From 18cfc36408fcb6ab2dd13d33ff02eb043ff77c36 Mon Sep 17 00:00:00 2001 From: Lancelot SIX Date: Sat, 23 Nov 2013 11:56:28 +0100 Subject: [PATCH 55/67] libi3/root_atom_contents: Free xcb reply structures Free memory allocated during xcb calls. --- libi3/root_atom_contents.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/libi3/root_atom_contents.c b/libi3/root_atom_contents.c index 697441eb..236f1b99 100644 --- a/libi3/root_atom_contents.c +++ b/libi3/root_atom_contents.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -51,20 +52,35 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); - if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + if (prop_reply == NULL) { + free(atom_reply); return NULL; + } + if (xcb_get_property_value_length(prop_reply) == 0) { + free(atom_reply); + free(prop_reply); + return NULL; + } if (prop_reply->type == XCB_ATOM_CARDINAL) { /* We treat a CARDINAL as a >= 32-bit unsigned int. The only CARDINAL * we query is I3_PID, which is 32-bit. */ - if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1) + if (asprintf(&content, "%u", *((unsigned int*)xcb_get_property_value(prop_reply))) == -1) { + free(atom_reply); + free(prop_reply); return NULL; + } } else { if (asprintf(&content, "%.*s", xcb_get_property_value_length(prop_reply), - (char*)xcb_get_property_value(prop_reply)) == -1) + (char*)xcb_get_property_value(prop_reply)) == -1) { + free(atom_reply); + free(prop_reply); return NULL; + } } if (provided_conn == NULL) xcb_disconnect(conn); + free(atom_reply); + free(prop_reply); return content; } From 755188220f56c995f71f97d833e52c7ac29602c6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Ouellet Date: Tue, 26 Nov 2013 05:41:56 -0500 Subject: [PATCH 56/67] fix the build on OS X OS X doesn't have posix_fallocate() yet, so put bf760d0241f0f078735e230b4bf6da4fc83368fe in #if defined(__APPLE__) the cd fails with: /bin/sh: line 0: cd: include: No such file or directory so give it a path relative to the top directory --- src/i3.mk | 4 ++-- src/log.c | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/i3.mk b/src/i3.mk index 36a24c8b..395b4cfa 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -54,12 +54,12 @@ src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stam i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec echo "[i3] Generating command parser" - (cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command) + (cd $(TOPDIR)/include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command) touch $@ i3-config-parser.stamp: generate-command-parser.pl parser-specs/config.spec echo "[i3] Generating config parser" - (cd include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config) + (cd $(TOPDIR)/include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config) touch $@ i3: libi3.a $(i3_OBJECTS) diff --git a/src/log.c b/src/log.c index 34e34532..86f47b9a 100644 --- a/src/log.c +++ b/src/log.c @@ -129,11 +129,16 @@ void open_logbuffer(void) { return; } +#if defined(__APPLE__) + if (ftruncate(logbuffer_shm, logbuffer_size) == -1) { + fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(errno)); +#else int ret; if ((ret = posix_fallocate(logbuffer_shm, 0, logbuffer_size)) != 0) { + fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(ret)); +#endif close(logbuffer_shm); shm_unlink(shmlogname); - fprintf(stderr, "Could not ftruncate SHM segment for the i3 log: %s\n", strerror(ret)); return; } From 2eea82eb0278343ccf76d40291348e368fed190a Mon Sep 17 00:00:00 2001 From: Jean-Philippe Ouellet Date: Tue, 26 Nov 2013 05:50:35 -0500 Subject: [PATCH 57/67] ignore symbol files on OS X (only for debugging, breaks git-add -A) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 600b4fb0..1d4c1678 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ include/all.h.pch *.swp *.gcda *.gcno +*.dSYM test.commands_parser test.config_parser testcases/MYMETA.json From eca5e4598a8d6623acb30e28bb023186d8f69bcc Mon Sep 17 00:00:00 2001 From: Lancelot SIX Date: Sat, 23 Nov 2013 12:03:55 +0100 Subject: [PATCH 58/67] libi3/root_atom_contents: handle data of arbitrary length Handle data fetched from xcb_get_property_unchecked with arbitrary length. This avoids having to rely on PATH_MAX macro where it is not necessary. --- common.mk | 2 +- libi3/root_atom_contents.c | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/common.mk b/common.mk index 0214abfa..e3c92fa1 100644 --- a/common.mk +++ b/common.mk @@ -144,7 +144,7 @@ PANGO_LIBS := $(call ldflags_for_lib, cairo) PANGO_LIBS += $(call ldflags_for_lib, pangocairo) # libi3 -LIBS = -L$(TOPDIR) -li3 +LIBS = -L$(TOPDIR) -li3 -lm ## Platform-specific flags diff --git a/libi3/root_atom_contents.c b/libi3/root_atom_contents.c index 236f1b99..00b74005 100644 --- a/libi3/root_atom_contents.c +++ b/libi3/root_atom_contents.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, xcb_intern_atom_cookie_t atom_cookie; xcb_intern_atom_reply_t *atom_reply; char *content; + size_t content_max_words = 256; xcb_connection_t *conn = provided_conn; if (provided_conn == NULL && @@ -50,12 +52,26 @@ char *root_atom_contents(const char *atomname, xcb_connection_t *provided_conn, xcb_get_property_cookie_t prop_cookie; xcb_get_property_reply_t *prop_reply; prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, - XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); if (prop_reply == NULL) { free(atom_reply); return NULL; } + if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) { + /* We received an incomplete value. Ask again but with a properly + * adjusted size. */ + content_max_words += ceil(prop_reply->bytes_after / 4.0); + /* Repeat the request, with adjusted size */ + free(prop_reply); + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL) { + free(atom_reply); + return NULL; + } + } if (xcb_get_property_value_length(prop_reply) == 0) { free(atom_reply); free(prop_reply); From 80df764e557cbf257ea561e6ffd58ba7428c7aa2 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sat, 23 Nov 2013 23:09:01 -0500 Subject: [PATCH 59/67] i3bar: Realign tray clients on map/unmap notify UnmapNotify events are interpreted by i3bar as an action taken by an application to hide its tray window. Likewise, MapNotify events are interpreted as an action taken by by an application to show its tray window. The actual cause of these events may be the application itself, or the result of some action taken by i3bar itself at the request of the application in the course of the XEmbed protocol. We respond by adjusting the size of the tray window and realigning any tray clients that remain. This will make room for the mapping window or close the gap left by the unmapping window when the bar is redrawn. fixes #1110 --- i3bar/src/xcb.c | 53 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index f25888b0..d3dc948e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -433,8 +433,9 @@ void handle_button(xcb_button_press_event_t *event) { } /* - * Configures the x coordinate of all trayclients. To be called after adding a - * new tray client or removing an old one. + * 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 + * unmapping a tray client window. * */ static void configure_trayclients(void) { @@ -610,7 +611,6 @@ static void handle_client_message(xcb_client_message_event_t* event) { } trayclient *tc = smalloc(sizeof(trayclient)); tc->win = client; - tc->mapped = map_it; tc->xe_version = xe_version; TAILQ_INSERT_TAIL(output->trayclients, tc, tailq); @@ -656,8 +656,36 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t* event) { } /* - * Handles UnmapNotify events. These events happen when a tray window unmaps - * itself. We then update our data structure + * Handles MapNotify events. These events happen when a tray client shows its + * window. We respond by realigning the tray clients. + * + */ +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; + } + } +} +/* + * Handles UnmapNotify events. These events happen when a tray client hides its + * window. We respond by realigning the tray clients. * */ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) { @@ -673,8 +701,8 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) { if (trayclient->win != event->window) continue; - DLOG("Removing tray client with window ID %08x\n", event->window); - TAILQ_REMOVE(walk->trayclients, trayclient, tailq); + 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(); @@ -741,15 +769,9 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { if (trayclient->mapped && !map_it) { /* need to unmap the window */ xcb_unmap_window(xcb_connection, trayclient->win); - trayclient->mapped = map_it; - configure_trayclients(); - draw_bars(false); } else if (!trayclient->mapped && map_it) { /* need to map the window */ xcb_map_window(xcb_connection, trayclient->win); - trayclient->mapped = map_it; - configure_trayclients(); - draw_bars(false); } free(xembedr); } @@ -836,9 +858,12 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { handle_destroy_notify((xcb_destroy_notify_event_t*) event); break; case XCB_UNMAP_NOTIFY: - /* UnmapNotifies are received when a tray window unmaps itself */ + /* UnmapNotify is received when a tray client hides its window. */ handle_unmap_notify((xcb_unmap_notify_event_t*) event); break; + case XCB_MAP_NOTIFY: + handle_map_notify((xcb_map_notify_event_t*) event); + break; case XCB_PROPERTY_NOTIFY: /* PropertyNotify */ handle_property_notify((xcb_property_notify_event_t*) event); From 454f11755e7d656174c7729e936f2839415d7bde Mon Sep 17 00:00:00 2001 From: Jean-Philippe Ouellet Date: Tue, 26 Nov 2013 23:13:30 -0500 Subject: [PATCH 60/67] Remove flex/bison from common.mk since they aren't used anymore --- common.mk | 2 -- 1 file changed, 2 deletions(-) diff --git a/common.mk b/common.mk index e3c92fa1..23ac8e34 100644 --- a/common.mk +++ b/common.mk @@ -2,8 +2,6 @@ UNAME=$(shell uname) DEBUG=1 COVERAGE=0 INSTALL=install -FLEX=flex -BISON=bison ifndef PREFIX PREFIX=/usr endif From 39f15da82f69cd91d50c2d398d636e5efbd2d829 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 1 Dec 2013 01:37:43 -0500 Subject: [PATCH 61/67] i3bar: Group child processes for signalling Set the process group id of the child process by calling `setpgid` after forking and before calling `exec`. The process group ID will be set to the process ID of the forked process. Processes spawned by this child process will also have this group ID. Send signals to the process group with `killpg`. This will send the signal to all of the process group. fixes #1128 --- i3bar/src/child.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index dce0218f..fd4185ee 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -424,6 +424,7 @@ void start_child(char *command) { dup2(pipe_in[1], STDOUT_FILENO); dup2(pipe_out[0], STDIN_FILENO); + setpgid(child.pid, 0); execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char*) NULL); return; default: @@ -507,8 +508,8 @@ void send_block_clicked(int button, const char *name, const char *instance, int void kill_child_at_exit(void) { if (child.pid > 0) { if (child.cont_signal > 0 && child.stopped) - kill(child.pid, child.cont_signal); - kill(child.pid, SIGTERM); + killpg(child.pid, child.cont_signal); + killpg(child.pid, SIGTERM); } } @@ -520,8 +521,8 @@ void kill_child_at_exit(void) { void kill_child(void) { if (child.pid > 0) { if (child.cont_signal > 0 && child.stopped) - kill(child.pid, child.cont_signal); - kill(child.pid, SIGTERM); + killpg(child.pid, child.cont_signal); + killpg(child.pid, SIGTERM); int status; waitpid(child.pid, &status, 0); cleanup(); @@ -535,7 +536,7 @@ void kill_child(void) { void stop_child(void) { if (child.stop_signal > 0 && !child.stopped) { child.stopped = true; - kill(child.pid, child.stop_signal); + killpg(child.pid, child.stop_signal); } } @@ -546,6 +547,6 @@ void stop_child(void) { void cont_child(void) { if (child.cont_signal > 0 && child.stopped) { child.stopped = false; - kill(child.pid, child.cont_signal); + killpg(child.pid, child.cont_signal); } } From 18ad1fd4de12e4da0d14437ff78145c8d17c21e4 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Mon, 2 Dec 2013 11:23:52 -0500 Subject: [PATCH 62/67] Refactor and improve test 514 Split test 514's assertion into three assertions to make it more explicit what is being tested, and why a run might fail. Move critical test code out of the event handler to clarify flow and allow a query of the actual current workspace to use in assertions. Works around an issue which caused this test to fail spurriously because of pointer-related quirks in the i3 test suite which would sometimes cause i3 to open on workspace 2 (However, the test is now agnostic to the initial workspace or output). --- testcases/t/514-ipc-workspace-multi-monitor.t | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/testcases/t/514-ipc-workspace-multi-monitor.t b/testcases/t/514-ipc-workspace-multi-monitor.t index 360bd426..61622ab0 100644 --- a/testcases/t/514-ipc-workspace-multi-monitor.t +++ b/testcases/t/514-ipc-workspace-multi-monitor.t @@ -19,6 +19,10 @@ use i3test i3_autostart => 0; +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + my $config = <connect()->recv; # Workspaces requests and events ################################ -my $focused = get_ws(focused_ws()); +my $old_ws = get_ws(focused_ws); # Events @@ -46,17 +50,11 @@ $i3->subscribe({ workspace => sub { my ($event) = @_; if ($event->{change} eq 'focus') { - # Check that we have the old and new workspace - $focus->send( - $event->{current}->{name} == '2' && - $event->{old}->{name} == $focused->{name} - ); + $focus->send($event); } } })->recv; -cmd 'focus output right'; - my $t; $t = AnyEvent->timer( after => 0.5, @@ -65,7 +63,15 @@ $t = AnyEvent->timer( } ); -ok($focus->recv, 'Workspace "focus" event received'); +cmd 'focus output right'; + +my $event = $focus->recv; + +my $current_ws = get_ws(focused_ws); + +ok($event, 'Workspace "focus" event received'); +is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace'); +is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace'); exit_gracefully($pid); From 1b640ae3bea048f971de6a33192a3f37e0700188 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Mon, 2 Dec 2013 18:06:19 -0500 Subject: [PATCH 63/67] Testcases: init pointer in a predictable position Tests may disturb the pointer in their normal operation that may lead to unexpected results in later tests using that display. Reset the pointer before a test begins to (0, 0) to save test developers from related "gotchas" and reduce multi-monitor test boilerplate. --- testcases/lib/i3test.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index 476cda03..414362ae 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -155,6 +155,9 @@ __ warnings->import; $x ||= i3test::X11->new; + # set the pointer to a predictable position in case a previous test has + # disturbed it + $x->root->warp_pointer(0, 0); $cv->recv if $i3_autostart; @_ = ($class); From 5a69bffbd61f67fd2f754332611b834779e72d36 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Fri, 6 Dec 2013 01:36:29 -0500 Subject: [PATCH 64/67] Remove pointer warp from test boilerplate Remove the line to warp the pointer to (0, 0) in `new-test` helper script, which is used to create new tests. Since 4.6-g0634766, testcases may assume at the start of the test that the pointer begins at position (0, 0). --- testcases/new-test | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testcases/new-test b/testcases/new-test index 14465d2d..4efcde5d 100755 --- a/testcases/new-test +++ b/testcases/new-test @@ -83,10 +83,6 @@ if ($multi_monitor) { print $fh <<'EOF'; use i3test i3_autostart => 0; -# Ensure the pointer is at (0, 0) so that we really start on the first -# (the left) workspace. -$x->root->warp_pointer(0, 0); - my $config = < Date: Wed, 11 Dec 2013 19:46:41 +0100 Subject: [PATCH 65/67] userguide: explain the difference between comma and semicolon for command chaining --- docs/userguide | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/userguide b/docs/userguide index db256cc4..666b9623 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1289,6 +1289,11 @@ should be affected by that command, by using various criteria. The criteria are specified before any command in a pair of square brackets and are separated by space. +When using multiple commands, separate them by using a +,+ (a comma) instead of +a semicolon. Criteria apply only until the next semicolon, so if you use a +semicolon to separate commands, only the first one will be executed for the +matched window(s). + *Example*: ------------------------------------ # if you want to kill all windows which have the class Firefox, use: @@ -1299,6 +1304,9 @@ bindsym $mod+x [class="(?i)firefox"] kill # kill only the About dialog from Firefox bindsym $mod+x [class="Firefox" window_role="About"] kill + +# enable floating mode and move container to workspace 4 +for_window [class="^evil-app$"] floating enable, move container to workspace 4 ------------------------------------ The criteria which are currently implemented are: From bfe32ad797a107e4d77897a49bb7094ac82c48f6 Mon Sep 17 00:00:00 2001 From: Tony Crisci Date: Sun, 15 Dec 2013 04:21:18 -0500 Subject: [PATCH 66/67] i3bar: Print error message when status_command fails Add a function to i3bar to print an error message in the status line when the child process invoked by status_command fails to provide input that can be displayed as a statusline. When the child provides JSON that cannot be parsed, alert the user and convey a short message provided by yajl communicating the specific problem. When the child (or the shell executing the status command) exits unexpectedly, alert the user and display the exit code. The cases where the status command is not executable or not found in the user's PATH are treated specially. fixes #1130 --- i3bar/src/child.c | 83 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index fd4185ee..1bd0d258 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,58 @@ char *statusline_buffer = NULL; int child_stdin; +/* + * Clears all blocks from the statusline structure in memory and frees their + * associated resources. + */ +static void clear_status_blocks() { + struct status_block *first; + while (!TAILQ_EMPTY(&statusline_head)) { + first = TAILQ_FIRST(&statusline_head); + I3STRING_FREE(first->full_text); + TAILQ_REMOVE(&statusline_head, first, blocks); + free(first); + } +} + +/* + * Replaces the statusline in memory with an error message. Pass a format + * string and format parameters as you would in `printf'. The next time + * `draw_bars' is called, the error message text will be drawn on the bar in + * the space allocated for the statusline. + */ + +/* forward function declaration is needed to add __attribute__ mechanism which + * helps the compiler understand we are defining a printf wrapper */ +static void set_statusline_error(const char *format, ...) __attribute__ ((format (printf, 1, 2))); + +static void set_statusline_error(const char *format, ...) { + clear_status_blocks(); + + char *message; + va_list args; + va_start(args, format); + vasprintf(&message, format, args); + + struct status_block *err_block = scalloc(sizeof(struct status_block)); + err_block->full_text = i3string_from_utf8("Error: "); + err_block->name = "error"; + err_block->color = "red"; + err_block->no_separator = true; + + struct status_block *message_block = scalloc(sizeof(struct status_block)); + message_block->full_text = i3string_from_utf8(message); + message_block->name = "error_message"; + message_block->color = "red"; + message_block->no_separator = true; + + TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks); + TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks); + + FREE(message); + va_end(args); +} + /* * Stop and free() the stdin- and sigchild-watchers * @@ -241,6 +294,7 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) { /* end of file, kill the watcher */ ELOG("stdin: received EOF\n"); cleanup(); + set_statusline_error("Received EOF from statusline process"); draw_bars(false); *ret_buffer_len = -1; return NULL; @@ -280,8 +334,18 @@ static bool read_json_input(unsigned char *input, int length) { #else if (status != yajl_status_ok && status != yajl_status_insufficient_data) { #endif - fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n", - status, length, input); + char *message = (char *)yajl_get_error(parser, 0, input, length); + + /* strip the newline yajl adds to the error message */ + if (message[strlen(message) - 1] == '\n') + message[strlen(message) - 1] = '\0'; + + fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n", + status, message, length, input); + + set_statusline_error("Could not parse JSON (%s)", message); + yajl_free_error(parser, (unsigned char*)message); + draw_bars(false); } else if (parser_context.has_urgent) { has_urgent = true; } @@ -351,10 +415,23 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) { * */ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) { + int exit_status = WEXITSTATUS(watcher->rstatus); + ELOG("Child (pid: %d) unexpectedly exited with status %d\n", child.pid, - watcher->rstatus); + exit_status); + + /* this error is most likely caused by a user giving a nonexecutable or + * nonexistent file, so we will handle those cases separately. */ + if (exit_status == 126) + set_statusline_error("status_command is not executable (exit %d)", exit_status); + else if (exit_status == 127) + set_statusline_error("status_command not found (exit %d)", exit_status); + else + set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status); + cleanup(); + draw_bars(false); } void child_write_output(void) { From 26e30ef00cfcb8e3b51ece71b407c7c6ff1d7666 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 Dec 2013 21:12:10 +0100 Subject: [PATCH 67/67] add release notes for v4.7 --- RELEASE-NOTES-4.7 | 82 +++++++++++++++++++++++++++++++++++++++++++++++ man/asciidoc.conf | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 RELEASE-NOTES-4.7 diff --git a/RELEASE-NOTES-4.7 b/RELEASE-NOTES-4.7 new file mode 100644 index 00000000..99c3dc1d --- /dev/null +++ b/RELEASE-NOTES-4.7 @@ -0,0 +1,82 @@ + + ┌──────────────────────────────┐ + │ Release notes for i3 v4.7 │ + └──────────────────────────────┘ + +This is the i3 v4.7. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +This release features a number of documentation improvements, better error +messages in various places, better tray compatibility in i3bar, and a number of +bugfixes. + +Relevant from a packaging point of view is that we have switched to the new +xcb-util-cursor library to get rid of libXcursor. The last remaining big piece +of Xlib code now is XKB, which we may be able to tackle in upcoming releases +thanks to the just released libxcb 1.10. + + ┌────────────────────────────┐ + │ Changes in v4.7 │ + └────────────────────────────┘ + + • docs/userguide: clarify variable parsing + • docs/userguide: clarify urgent_workspace + • docs/userguide: add proper quoting for rename sample command + • docs/userguide: clarify multiple criteria + • docs/userguide: userguide: explain the difference between comma and semicolon for command chaining + • docs/hacking-howto: update to reflect parser changes + • man/i3-dump-log: document -f + • switch from libXcursor to xcb-util-cursor + • Respect workspace numbers when looking for a free workspace name + • Revert "raise fullscreen windows on top of all other X11 windows" + • i3bar: Create pixmaps using the real bar height, rather than screen height + • Add scratchpad bindings to the default config + • Close all children when closing a workspace + • i3bar: Add new bar.binding_mode_indicator configuration + • Improve error message when $XDG_RUNTIME_DIR is not writable + • libi3/font: Draw the text at the expected place + • libi3/font: Set DPI for the pango context + • Add ability to escape out of a mouse-resize operation + • Do not resize/reposition floating containers when moving them to scratchpad + • i3-nagbar: Set button inner-width to the width of the label + • Assigned windows open urgent when not visible + • i3bar: Only configure tray on own outputs + • Command 'move ' moves across outputs + • i3bar: Handle DestroyNotify events + • i3bar: Realign tray clients on map/unmap notify + • i3bar: Group child processes for signalling + • i3bar: Print error message when status_command fails + • Remove references to PATH_MAX macro for GNU/Hurd + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • update root geometry on output changes for “fullscreen global” + • don’t flatten tabbed/stacked containers + • Fix handling of new windows with WM_STATE_FULLSCREEN + • correctly recognize assigned windows as urgent + • Fix keyboard and mouse resize in nested containers + • Reply to _NET_REQUEST_FRAME_EXTENTS correctly + • Fix command parser: resizing tiling windows + • Fix output retrieval for floating cons + • Use _PATH_BSHELL to ensure using a bourne shell + • Instead of crashing, return DRAG_ABORT on UnmapNotify from drag_pointer + • Remove-child callback skips output content cons + • ignore _NET_ACTIVE_WINDOW for scratchpad windows + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + +Alexander Neumann, badboy, Baptiste Daroussin, Bas Pape, Deiz, Franck Michea, +Jean-Philippe Ouellet, jj, jookia, kaersten, Lancelot SIX, Leo Gaspard, +mistnim, Peter Maatman, Quentin Glidic, Sebastian Ullrich, Slava, syl20bnr, +Tony Crisci, Trung Ngo, Vivien Didelot, Xarthisius + +I want to specifically thank Tony Crisci for the very valuable help with +responding to bugreports in our bugtracker. Thank you! + +-- Michael Stapelberg, 2013-12-22 diff --git a/man/asciidoc.conf b/man/asciidoc.conf index aa0639d5..a5dff477 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -4.6 +4.7 i3 Manual