]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Fri, 22 Nov 2013 16:09:28 +0000 (17:09 +0100)
committerMichael Stapelberg <michael@stapelberg.de>
Fri, 22 Nov 2013 16:09:28 +0000 (17:09 +0100)
74 files changed:
.gitignore
DEPENDS
common.mk
debian/changelog
debian/control
debian/rules
docs/hacking-howto
docs/ipc
docs/userguide
i3-config-wizard/main.c
i3-dump-log/main.c
i3-input/main.c
i3-msg/main.c
i3-nagbar/main.c
i3.config
i3bar/include/config.h
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/xcb.c
include/config.h
include/config_directives.h
include/floating.h
include/libi3.h
include/resize.h
include/xcursor.h
libi3/font.c
libi3/get_process_filename.c
libi3/root_atom_contents.c
man/i3-dump-log.man
parser-specs/commands.spec
parser-specs/config.spec
src/click.c
src/commands.c
src/con.c
src/config_directives.c
src/display_version.c
src/floating.c
src/ipc.c
src/main.c
src/manage.c
src/move.c
src/resize.c
src/scratchpad.c
src/startup.c
src/tree.c
src/workspace.c
src/xcursor.c
testcases/.gitignore
testcases/Makefile.PL
testcases/complete-run.pl
testcases/lib/StartXDummy.pm
testcases/lib/i3test.pm
testcases/new-test
testcases/t/100-fullscreen.t
testcases/t/113-urgent.t
testcases/t/119-match.t
testcases/t/129-focus-after-close.t
testcases/t/165-for_window.t
testcases/t/166-assign.t
testcases/t/173-regress-focus-assign.t
testcases/t/177-bar-config.t
testcases/t/185-scratchpad.t
testcases/t/187-commands-parser.t
testcases/t/190-scratchpad-diff-ws.t
testcases/t/200-urgency-timer.t
testcases/t/201-config-parser.t
testcases/t/203-regress-assign-and-move.t
testcases/t/204-regress-scratchpad-move.t
testcases/t/208-regress-floating-criteria.t
testcases/t/211-regress-urgency-assign.t
testcases/t/212-assign-urgency.t [new file with mode: 0644]
testcases/t/502-focus-output.t
testcases/t/515-create-workspace.t [new file with mode: 0644]
testcases/t/516-move.t [new file with mode: 0644]

index 0160a2467615241286a85a85e66195864de33c8e..600b4fb0b62de44a3dfb18d6fdcd99ef80b34899 100644 (file)
@@ -31,3 +31,4 @@ docs/*.html
 !/docs/refcard.html
 i3-command-parser.stamp
 i3-config-parser.stamp
+.clang_complete
diff --git a/DEPENDS b/DEPENDS
index 4b0902723a179ee6d1a4ed31102b61eeceb78bd0..083372c7ec17e950e7887ea22c9d08c1ae6f3e54 100644 (file)
--- a/DEPENDS
+++ b/DEPENDS
 │ 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/     │
 │ 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
@@ -26,6 +26,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.
index de5c7e9894221e01986972caa17c57a89972acbc..0214abfa52d9e18d956abff02ac851afb5edfc84 100644 (file)
--- 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)
index 80a20e3c2d092e10066df87bb3a5db21d72d528e..f38553639d37113021bc93c7348db0c018c45706 100644 (file)
@@ -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 <stapelberg@debian.org>  Mon, 18 Mar 2013 23:01:30 +0100
+ -- Michael Stapelberg <stapelberg@debian.org>  Wed, 07 Aug 2013 20:53:26 +0200
+
+i3-wm (4.6-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  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 <stapelberg@debian.org>  Tue, 14 May 2013 20:48:16 +0200
 
 i3-wm (4.5.1-1) experimental; urgency=low
 
index f9ecb5582319e63446cddc6c93ed6ff33c5a44b8..558b01270b748aa80d87882ebb505fe816ae4a29 100644 (file)
@@ -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,
@@ -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
index 3ae79dddf30ae59db5de256e4db3422b10e19f2d..55c72b514ead804081879a20e9362bf8150c06ce 100755 (executable)
@@ -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
index 73f8e88ab4ff22e4a31511bc4bea56a843db2336..bc59eaeb7a3384afab05025f510f992e195f81c6 100644 (file)
@@ -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
index 913899cc8a7955ba08b6d9ff48d6ee74474ff01e..85e5e77e3e7966122f2835c716ac31d07ee35aef 100644 (file)
--- 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",
index 7e5f51eefefcb4eb743a9f64babb74828668db5f..db256cc48f0b6c0fa4dfe66b5f3d23413ad3ba19 100644 (file)
@@ -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*:
 ----------------------------------------------
@@ -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
 
@@ -833,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
@@ -899,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.
 
@@ -1179,11 +1180,32 @@ workspace_buttons <yes|no>
 --------------------------
 
 *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 <<resizingconfig>>.
+
+The default is to show the mode indicator.
+
+*Syntax*:
+-------------------------------
+binding_mode_indicator <yes|no>
+-------------------------------
+
+*Example*:
+-----------------------------
+bar {
+    binding_mode_indicator no
+}
+-----------------------------
 
 === Colors
 
@@ -1210,7 +1232,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*:
 ----------------------------------------
@@ -1263,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:
@@ -1553,7 +1579,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*:
@@ -1568,7 +1594,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
index ffc3df93011869e88561e523445fa9e267aeb68e..880b80ed8b4b84124c41325b9e733cd85856d57a 100644 (file)
@@ -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)))
index 852a5cf7b2fc944e50272fcd2c1a9b676c2070f6..acb7fcb1f36308b64a2f075b038668c20f55f00c 100644 (file)
@@ -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);
index 49db4df231dd234260e30369d3f589353140b78f..da95c903fae825cf762d7633bcc7dd0bac2fa555 100644 (file)
@@ -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);
index a1428fb8f5f89aac9e35f68420f983b808ee7303..935edc0467609a330ce6d3640778ab4f61bd764c 100644 (file)
@@ -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)
index a9619f9620913c9bcc0920343adb9946199358ef..0fa75f8e0550dc9b00dd78b84c8826179b7d69ad 100644 (file)
@@ -22,6 +22,7 @@
 #include <getopt.h>
 #include <limits.h>
 #include <fcntl.h>
+#include <paths.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
@@ -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);
@@ -198,8 +192,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 +219,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 +229,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 +253,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);
 
index ff6c9dbe8282f02759cf9d79a9026005cdb102bc..de7e1fec5ae1c3cc603fcac8925824a1a35d17f4 100644 (file)
--- 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
index 4c01d68cec4d8aeb03be106081dd4f9576f034ab..c648671259e3c1d8d9ddeeaed81ed75fce967f82 100644 (file)
@@ -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;
index 4e5e49c928edf37f1e3ff450c0cb5b5c0f87ddaa..dce0218fd305fda1ee38be246a47cc2e3618f249 100644 (file)
@@ -22,6 +22,7 @@
 #include <yajl/yajl_parse.h>
 #include <yajl/yajl_version.h>
 #include <yajl/yajl_gen.h>
+#include <paths.h>
 
 #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 */
index f5a2a342f48d756adca0ac79a238addac134fc7e..5ac31b1f3a16d53a3a2d2488377b7b22374c62fd 100644 (file)
@@ -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;
index 15c68a0876dde8358ffa73f0a7b4461848b068ce..0c8de65ec5baeb5a8025deaee577f951ca3118b5 100644 (file)
@@ -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,
@@ -957,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") ||
@@ -1061,6 +1045,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 +1319,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 +1392,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 +1406,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 +1467,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;
@@ -1527,10 +1512,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 {
@@ -1541,9 +1536,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 +1564,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) {
@@ -1612,9 +1607,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) {
@@ -1631,7 +1623,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,
@@ -1666,72 +1658,64 @@ void draw_bars(bool unhide) {
                           MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font.height + 2);
         }
 
-        if (config.disable_ws) {
-            continue;
-        }
-
-        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 (!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 (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));
+                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;
                 }
-            }
-            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;
+                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;
@@ -1770,13 +1754,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();
     }
 
index c7479b3afeabec83ac8d3a32096f6a300c762e98..4267dcfe3d335875452193578c5196029de9b983 100644 (file)
@@ -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;
 
index f9b7a47fdf2493c5b2af6101750b35f772b05932..9569a7b0af5b1d5904a5405f1bacc9161bb45d79 100644 (file)
@@ -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);
 
index c8586527a02cec97898a222138459cac32159469..43600187f60d5e4db3e386a93f7d84e5415e0bde 100644 (file)
@@ -134,14 +134,37 @@ 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.
+ *
+ * DRAGGING will indicate the drag action is still in progress and can be
+ * continued or resolved.
+ *
+ * DRAG_SUCCESS will indicate the intention of the drag action should be
+ * carried out.
+ *
+ * DRAG_REVERT will indicate an attempt should be made to restore the state of
+ * the involved windows to their condition before the drag.
+ *
+ * DRAG_ABORT will indicate that the intention of the drag action cannot be
+ * carried out (e.g. because the window has been unmapped).
+ *
+ */
+typedef enum {
+    DRAGGING = 0,
+    DRAG_SUCCESS,
+    DRAG_REVERT,
+    DRAG_ABORT
+} drag_result_t;
+
+/**
+ * This function grabs your pointer and keyboard and lets you drag stuff around
+ * (borders). Every time you move your mouse, an XCB_MOTION_NOTIFY event will
+ * be received and the given callback will be called with the parameters
+ * specified (client, border on which the click originally was), the original
+ * rect of the client, the event and the new coordinates (x, y).
  *
  */
-void drag_pointer(Con *con, const xcb_button_press_event_t *event,
+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);
 
index b0141f1df9cc22c8eae3e6abf4b2961967ce0330..9ba780042310128c0530674028cb9520f7779cc9 100644 (file)
@@ -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
index fa0216c88ac3597e45193a32d7b1376e874d9860..ae26ee992a380b4462e714e5b59ec0db7725e5d0 100644 (file)
@@ -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
index bfe37c3944cf098e22b91aa461cb92e7db5dde5c..868fee7823647dadc30a0718269ec792e816ef2c 100644 (file)
@@ -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 <X11/Xlib.h>
+#include <xcb/xcb_cursor.h>
 
 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);
 
 /**
index 8239b1f4414e8cc2ea6974c35da66c8042a7b6a4..4c064f2bdc038d0c5e99053718eb72c9ab4c559a 100644 (file)
@@ -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,7 +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);
-    PangoLayout *layout = pango_cairo_create_layout(cr);
+    PangoLayout *layout = create_layout_with_dpi(cr);
     pango_layout_set_font_description(layout, font->specific.pango_desc);
 
     /* Get the font height */
@@ -85,17 +100,21 @@ 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);
+    PangoLayout *layout = create_layout_with_dpi(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 */
@@ -113,7 +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);
-    PangoLayout *layout = pango_cairo_create_layout(cr);
+    PangoLayout *layout = create_layout_with_dpi(cr);
 
     /* Get the font width */
     gint width;
index 630e3d1caa986e122f3e44d2f3811650ef375a5d..941d4439e75177161b09d3fdca06597a6aa8c24c 100644 (file)
@@ -15,6 +15,7 @@
 #include <sys/types.h>
 #include <pwd.h>
 #include <unistd.h>
+#include <err.h>
 
 #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;
                 }
index cabaaf2c3965f70c27be20937dbee3c377cc8ccf..697441ebadf8588f254bb53597032b4ab06dc4b4 100644 (file)
  * 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;
 }
 
index eb8ba2f76286c6d10697abdef79c4291371d36f0..455144071c0d81a6322a0a3386ffeb1d786e23ec 100644 (file)
@@ -1,7 +1,7 @@
 i3-dump-log(1)
 ==============
-Michael Stapelberg <michael+i3@stapelberg.de>
-v4.1, December 2011
+Michael Stapelberg <michael@i3wm.org>
+v4.6, September 2013
 
 == NAME
 
@@ -9,7 +9,7 @@ i3-dump-log - dumps the i3 SHM log
 
 == SYNOPSIS
 
-i3-dump-log [-s <socketpath>]
+i3-dump-log [-s <socketpath>] [-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
index 2c640c65ae9e5f6a69daaba084641a2003de6722..e3da62c91aae2b2ca39c6d19f6163a06b91b3f6b 100644 (file)
@@ -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 <name> to <name>
index fd13797b15d22710d12f8c5001b2463e6a6a0519..dfd6401d4893ff9458e3a58aaf14bac64598cdd4 100644 (file)
@@ -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
index 3022c2480b3eaaddde03c210ad5bcc9dea38d319..340c8414696040313fc30d4231d61580c6e90a11 100644 (file)
@@ -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;
index cde1cd7c115f34139c6f6e8d38e32b77d82833f1..32fdc7f170b957d190ce6a719f18978a254ba697 100644 (file)
     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
@@ -69,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
@@ -441,12 +460,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 +549,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;
     }
 
@@ -618,79 +627,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;
-
-    /* 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);
+    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;
 
-    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");
     }
@@ -939,13 +919,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;
     }
@@ -1085,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);
 
@@ -1167,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);
@@ -1439,12 +1413,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;
     }
@@ -1521,7 +1490,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));
     }
 
@@ -1713,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);
@@ -1750,12 +1719,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 +1754,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 +1849,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 +1862,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;
     }
 
@@ -1950,7 +1899,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 +1944,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;
index d5b8c140bda77b17797229bb7ae85fb6af501bc5..ba14e06c66028083c46da87a16c5f7ed0dc75562 100644 (file)
--- 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) {
index 0fac7006b807c4ae1b549e0fa0b2a4fe381cb276..b28ad49d3d9c66e68e1b7195b2f4a884225890b2 100644 (file)
 
 #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.
  ******************************************************************************/
@@ -550,6 +540,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);
 }
index 0901ae07b4cfea2cb391e5e5cd89899605e2c386..5fa4f8e2234945831776b36be875b444e177de15 100644 (file)
@@ -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");
index 643f204b64110a12aede45c4bf4b6ee9e4851f86..10962403b8fa09775f210c8951391029a43ef940 100644 (file)
@@ -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 revert/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_REVERT)
+        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, &params);
+    /* 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, &params);
+
+    /* If the user cancels, undo the resize */
+    if (drag_result == DRAG_REVERT)
+        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;
@@ -569,7 +583,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 */
@@ -587,18 +601,39 @@ 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_ABORT;
     }
 
     free(reply);
 
+    /* Grab the keyboard */
+    xcb_grab_keyboard_cookie_t keyb_cookie;
+    xcb_grab_keyboard_reply_t *keyb_reply;
+
+    keyb_cookie = xcb_grab_keyboard(conn,
+            false, /* get all keyboard events */
+            root, /* grab the root window */
+            XCB_CURRENT_TIME,
+            XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */
+            XCB_GRAB_MODE_ASYNC /* keyboard mode */
+            );
+
+    if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, NULL)) == NULL) {
+        ELOG("Could not grab keyboard\n");
+        return DRAG_ABORT;
+    }
+
+    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;
+    Con *inside_con = NULL;
+
+    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 */
@@ -611,7 +646,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
 
             switch (type) {
                 case XCB_BUTTON_RELEASE:
-                    loop_done = true;
+                    drag_result = DRAG_SUCCESS;
                     break;
 
                 case XCB_MOTION_NOTIFY:
@@ -621,11 +656,26 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
                     break;
 
                 case XCB_UNMAP_NOTIFY:
+                    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;
+
                 case XCB_KEY_PRESS:
-                case XCB_KEY_RELEASE:
-                    DLOG("Unmap-notify, aborting\n");
+                    /* Cancel the drag if a key was pressed */
+                    DLOG("A key was pressed during drag, reverting changes.");
+                    drag_result = DRAG_REVERT;
+
                     handle_event(type, inside_event);
-                    loop_done = true;
                     break;
 
                 default:
@@ -638,7 +688,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
                 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;
@@ -648,8 +698,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;
 }
 
 /*
index 4c41465b7636aa0766429747ecf1cc41462c58af..a928dba9007a8c0cd79d56417d60dc8b332e6051 100644 (file)
--- 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);
 
index aac738831915fc8a003626bfd5f64595373d4d88..2b397181fc88088e60f43faa0956768cf2dd52e8 100644 (file)
@@ -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;
index bb376f60bfaa16497e097effd7d5cb9615e946cf..af9e8ef2cbf4d3d0c646dc05b29c37b8041436ea 100644 (file)
@@ -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) {
@@ -322,11 +328,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) {
@@ -430,12 +445,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 (=
index 46b90177dd27dbbd27024bf8d820994155754760..0b3ab66011d4d9d189badcebcf1ea11ae81e180e 100644 (file)
@@ -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);
index 268dc3fbd119b69780bcbee89f7d47cc195829d2..cc4ba841f8b34377d353ad4eea46787dca3be4dd 100644 (file)
@@ -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");
 
@@ -106,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, &params);
+    drag_result_t drag_result = drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
 
     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_REVERT)
+        return 0;
+
     int pixels;
     if (orientation == HORIZ)
         pixels = (new_position - event->root_x);
index ce3d9b9ba3c38b4b8c31605707f9805ad776400c..17df77dde624804e642a0934c9157a06bef07e18 100644 (file)
@@ -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;
+        }
     }
 }
 
index ee51664fa3dd7ab360ed5674d87667df772fb018..85e5dbc2e400b75651398cd2c0746677811b2ee6 100644 (file)
@@ -17,6 +17,7 @@
 
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <paths.h>
 
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-launcher.h>
@@ -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);
index 58af674b28fd3e6b0980458ad584eba6115383ed..836183ec114439aa2b5c6cac9092abd3b095dd4d 100644 (file)
@@ -359,15 +359,24 @@ 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);
     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->focus_head)); child; ) {
+            nextchild = TAILQ_NEXT(child, focused);
+            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);
 }
index af9325f78b6bc4b299ee7fff2ed853703cfd6c50..3f70ced7bb32f1150306ebb5b341c7a08e43fa61 100644 (file)
@@ -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);
 
index 90fd69dd237a67cc7470f238af33a693e6175aad..dcbe2ad0d8cd5c0c9b678dab97d394e7e94bcdae 100644 (file)
@@ -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 <assert.h>
-#include <X11/Xcursor/Xcursor.h>
-#include <X11/cursorfont.h>
+#include <xcb/xcb_cursor.h>
 
 #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];
 }
index c11c55636aecd695af5fb8fba34b753ec7f36a7e..294f0daef79b134b469575167daf246a0dc152fe 100644 (file)
@@ -8,3 +8,4 @@ inc
 META.yml
 i3-cfg-for-*
 -
+Xdummy.so
index 28036d1fd390dae998e3de8548e240edcace0514..6bc80d8f3ded97c86f5a76b4e9db7e830c3787b7 100755 (executable)
@@ -8,8 +8,8 @@ WriteMakefile(
     MIN_PERL_VERSION => '5.010000', # 5.10.0
     PREREQ_PM => {
         'AnyEvent'     => 0,
-        'AnyEvent::I3' => '0.14',
-        'X11::XCB'     => '0.03',
+        'AnyEvent::I3' => '0.15',
+        'X11::XCB'     => '0.09',
         'Inline'       => 0,
         'ExtUtils::PkgConfig' => 0,
         'Test::More'   => '0.94',
index 560dd4c3de0bc21f6c4126b7f152ef4b424d89fb..7ca89016f6ea45443372cba02cd50ac6f9c93d22 100755 (executable)
@@ -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";
 
 
@@ -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);
 }
 
index f2ebcadd16fb1f67ee87e040fe880f244da39002..592feb8501ea4a0c4b0e239e6ceb60e1b16ca1e1 100644 (file)
@@ -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++;
index 9f3a6ea2d0be346be4363dd074c8cac69a19a8cd..476cda03e732d656bbc68f7f1c6feb95fc05afbf 100644 (file)
@@ -406,7 +406,7 @@ C<fresh_workspace> 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
index c2546158267fa2b5a49bf75579b936fea7e9dd7a..14465d2dc2494871fb81fd335d6f371980717dac 100755 (executable)
@@ -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
index cec7000aa9146ea0ef8de534554c88b24c96fae3..54b29c9de331e33ed0b1c348689bff361e2f7a68 100644 (file)
@@ -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;
index 2eb853de1cd958c4880ac934aee76ba7698b7a9a..bb913819b108526371dbc1430813197e8d7dd80a 100644 (file)
@@ -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
index 7ac622c72457777c6610a047532ce7abd1fa5b55..0aaeddd732ddfc676380cc451f0c2f28fd666fe3 100644 (file)
@@ -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');
 
index df226e84070bf826d9667af576b218df9a13382f..2ebfd61022012c5a913b53a53f614ba260f91d68 100644 (file)
@@ -119,6 +119,55 @@ 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');
+
+################################################################################
+# 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
index b01de91d3b398a2bc0afc4561178f5f407a256df..ada7c5c5bd67c98f79d2be3d088027d54f1c3016 100644 (file)
@@ -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)};
index 1cee1ebbeeed77f0b498c5c5d3b23e5fb0e02905..355058dd01314feecd131b3d13e19a919c286368 100644 (file)
 # 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;
index aff3fe3a61c6f9711b26a5bf0c6056ae1f495f63..b010963b1231cc391535e561d2df37d421235669 100644 (file)
 # 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;
index 762e52b87af6e1598da68583c64cccc8c2b4abde..8675dd7183c003c335753308d140eef89b050867 100644 (file)
@@ -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 = <<EOT;
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
 bar {
-    # Start a default instance of i3bar which provides workspace buttons.
+    # Start a default instance of i3bar which does not provide
+    # workspace buttons.
     # Additionally, i3status will provide a statusline.
     status_command i3status --bar
 
@@ -98,6 +100,7 @@ bar {
     mode dock
     font Terminus
     workspace_buttons no
+    binding_mode_indicator no
     verbose yes
     socket_path /tmp/foobar
 
@@ -125,6 +128,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
 is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
 ok($bar_config->{verbose}, 'verbose on');
 ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
+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 = <<EOT;
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
 bar {
-    # Start a default instance of i3bar which provides workspace buttons.
+    # Start a default instance of i3bar which does not provide
+    # workspace buttons.
     # Additionally, i3status will provide a statusline.
     status_command i3status --bar
 
@@ -243,6 +248,7 @@ bar {
     mode dock
     font Terminus
     workspace_buttons no
+    binding_mode_indicator yes
     verbose yes
     socket_path /tmp/foobar
 
@@ -271,6 +277,7 @@ $bar_config = $i3->get_bar_config($bar_id)->recv;
 is($bar_config->{status_command}, 'i3status --bar', 'status_command correct');
 ok($bar_config->{verbose}, 'verbose on');
 ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled');
+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');
index 6ee877bd288dd22625888fec93b861903a0995b8..5901f99cfb6538fc273ac0ccf5ceb404c23eb656 100644 (file)
@@ -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;
index bbb89d93be9d902aaf56089e2d3f78bddb0f9a6e..5ee94f876f141b5c8133b47c506582ef9259a10a 100644 (file)
@@ -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: <word>\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;
index 5451f482272cd2e366dd758c9ae93b54535e2747..644aafb130943225d2e03af15b653ec237c97499 100644 (file)
 # 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');
index 730a950a4aea8f316056f77e8d23c76831c365ed..0fb8c8be364a09a638bcbc5c7d858fdeafdedab3 100644 (file)
@@ -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;
 
index 06588b111a617b10bdab7382eeadeca1bef692ac..55239c62d749405f0a51bc8bf1515b7c4f2be3a0 100644 (file)
@@ -627,7 +627,7 @@ EOT
 
 $expected = <<'EOT';
 cfg_bar_output(LVDS-1)
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', '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: <end>, '#', '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 <stdin>)
 ERROR: CONFIG: Line   1: bar {
 ERROR: CONFIG: Line   2:     output LVDS-1
index bf3df91ee4f70b21a711dabec7895cf7fb5f24ab..51a1676f02b68fdff469ed200b167a81c82be052 100644 (file)
@@ -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 = <<EOT;
 # i3 config file (v4)
@@ -31,28 +30,10 @@ EOT
 
 my $pid = launch_with_config($config);
 
-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"
-    );
-}
-
 # 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;
index 8c307aa5820921e925daeaa7e08e5303c2b099ee..0782da387d5eed868bbd2f4f14ace41f0673b3b0 100644 (file)
 # 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;
 
index 09a29fd8aca4015f27eedf6daf9c51e42a395dab..a13e0ad36695f758ef1f21ac8198e83dfab4a401 100644 (file)
@@ -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;
index a90703ee894b75efa2bcf6131dd79d0442ce03d7..57d8b434d116c41e96953379703beb992f7977a7 100644 (file)
 # 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');
diff --git a/testcases/t/212-assign-urgency.t b/testcases/t/212-assign-urgency.t
new file mode 100644 (file)
index 0000000..f53eb7e
--- /dev/null
@@ -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 = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign [class="special"] → targetws
+EOT
+
+my $pid = launch_with_config($config);
+
+cmd 'workspace ordinaryws';
+my $window = open_special;
+sync_with_i3;
+
+ok(get_ws('targetws')->{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 = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+assign [class="special"] → targetws
+EOT
+
+$pid = launch_with_config($config);
+
+cmd 'workspace targetws';
+$window = open_special;
+sync_with_i3;
+
+ok(!get_ws('targetws')->{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 = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+workspace targetws output fake-0
+workspace ordinaryws output fake-1
+
+assign [class="special"] → targetws
+EOT
+
+$pid = launch_with_config($config);
+
+cmd 'workspace ordinaryws';
+$window = open_special;
+sync_with_i3;
+
+ok(!get_ws('targetws')->{urgent}, 'target workspace is not urgent');
+
+$window->destroy;
+
+exit_gracefully($pid);
+
+done_testing;
index a6c5583fee5614f44951161dba1c9d0e7430e7b8..8c1c36c5a9f551cb06678b881180ffced0c875d5 100644 (file)
@@ -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;
diff --git a/testcases/t/515-create-workspace.t b/testcases/t/515-create-workspace.t
new file mode 100644 (file)
index 0000000..be790bf
--- /dev/null
@@ -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 = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+
+bindsym 1 workspace 1: eggs
+EOT
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path());
+my $ws = $i3->get_workspaces->recv;
+
+is($ws->[0]->{name}, '1: eggs', 'new workspace uses config name');
+is($ws->[1]->{name}, '2', 'naming continues with next free number');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/516-move.t b/testcases/t/516-move.t
new file mode 100644 (file)
index 0000000..0d21ca3
--- /dev/null
@@ -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 <direction>' 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 = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768
+
+workspace left-top output fake-0
+workspace right-top output fake-1
+workspace right-bottom output fake-2
+workspace left-bottom output fake-3
+EOT
+
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Try to move a single window across outputs in each direction
+#####################################################################
+
+cmd('workspace left-top');
+my $alone_window = open_window;
+
+cmd('move right');
+is(scalar @{get_ws_content('right-top')}, 1, 'moved individual window to right-top workspace');
+
+cmd('move down');
+is(scalar @{get_ws_content('right-bottom')}, 1, 'moved individual window to right-bottom workspace');
+
+cmd('move left');
+is(scalar @{get_ws_content('left-bottom')}, 1, 'moved individual window to left-bottom workspace');
+
+cmd('move up');
+is(scalar @{get_ws_content('left-top')}, 1, 'moved individual window to left-top workspace');
+
+$alone_window->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->{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 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;