]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'master' into next
authorMichael Stapelberg <michael@stapelberg.de>
Fri, 28 Sep 2012 17:47:16 +0000 (19:47 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Fri, 28 Sep 2012 17:47:16 +0000 (19:47 +0200)
100 files changed:
debian/changelog
debian/patches/manpage-x-terminal-emulator.patch
debian/patches/use-x-terminal-emulator.patch
debian/rules
docs/i3bar-protocol
docs/ipc
docs/userguide
i3-config-wizard/xcb.h
i3-input/i3-input.h
i3-nagbar/i3-nagbar.h
i3bar/include/child.h
i3bar/include/common.h
i3bar/include/determine_json_version.h [deleted file]
i3bar/include/parse_json_header.h [new file with mode: 0644]
i3bar/include/util.h
i3bar/include/xcb.h
i3bar/src/child.c
i3bar/src/determine_json_version.c [deleted file]
i3bar/src/ipc.c
i3bar/src/parse_json_header.c [new file with mode: 0644]
i3bar/src/xcb.c
include/all.h
include/assignments.h
include/click.h
include/cmdparse.h
include/commands.h
include/commands_parser.h
include/con.h
include/config.h
include/data.h
include/debug.h
include/display_version.h
include/ewmh.h
include/fake_outputs.h
include/floating.h
include/handlers.h
include/i3.h
include/i3/ipc.h
include/ipc.h
include/key_press.h
include/libi3.h
include/load_layout.h
include/log.h
include/manage.h
include/match.h
include/move.h
include/output.h
include/randr.h
include/regex.h
include/render.h
include/resize.h
include/scratchpad.h
include/shmlog.h
include/sighandler.h
include/startup.h
include/tree.h
include/util.h
include/window.h
include/workspace.h
include/x.h
include/xcb.h
include/xcb_compat.h
include/xcursor.h
include/xinerama.h
parser-specs/commands.spec
src/cfgparse.l
src/cfgparse.y
src/click.c
src/commands.c
src/con.c
src/config.c
src/floating.c
src/handlers.c
src/ipc.c
src/load_layout.c
src/main.c
src/randr.c
src/render.c
src/resize.c
src/scratchpad.c
src/tree.c
src/workspace.c
src/x.c
src/xcursor.c
testcases/Makefile.PL
testcases/complete-run.pl
testcases/t/113-urgent.t
testcases/t/116-nestedcons.t
testcases/t/117-workspace.t
testcases/t/132-move-workspace.t
testcases/t/161-regress-borders-restart.t
testcases/t/169-border-toggle.t
testcases/t/174-border-config.t
testcases/t/176-workspace-baf.t
testcases/t/185-scratchpad.t
testcases/t/198-regression-scratchpad-crash.t
testcases/t/199-ipc-mode-event.t [new file with mode: 0644]
testcases/t/200-urgency-timer.t [new file with mode: 0644]
testcases/t/504-move-workspace-to-output.t
testcases/t/506-focus-right.t [new file with mode: 0644]

index 89184c130a22b5480d9ec1779b731cd570f0649c..c4c2681c47ffa227ff076968796f354fc1554d25 100644 (file)
@@ -1,8 +1,14 @@
-i3-wm (4.2.1-0) unstable; urgency=low
+i3-wm (4.3.1-0) unstable; urgency=low
 
   * NOT YET RELEASED
 
- -- Michael Stapelberg <michael@stapelberg.de>  Fri, 27 Jan 2012 19:34:11 +0000
+ -- Michael Stapelberg <michael@stapelberg.de>  Wed, 19 Sep 2012 18:13:15 +0200
+
+i3-wm (4.3-1) experimental; urgency=low
+
+  * New upstream release
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Wed, 19 Sep 2012 18:13:40 +0200
 
 i3-wm (4.2-1) unstable; urgency=low
 
index ed6cb465f542b1c1cf5b764513c76d56744f0be4..17b8ee4cb9ba7522e3e38f717b021ca1c4363a3d 100644 (file)
@@ -1,3 +1,11 @@
+Description: list x-terminal-emulator as one of i3-sensible-terminal’s choices
+Author: Michael Stapelberg <stapelberg@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2011-12-28
+
+---
+
 Index: i3-4.1.1/man/i3-sensible-terminal.man
 ===================================================================
 --- i3-4.1.1.orig/man/i3-sensible-terminal.man 2011-12-28 23:56:55.487581000 +0100
index 0d71f7c4d3fe14f8dee694d500782ae12a7188eb..9616162493dd78f3b3c400f934fa3f5def731545 100644 (file)
@@ -1,7 +1,15 @@
-Index: i3-4.1.1/i3-sensible-terminal
+Description: i3-sensible-terminal: try x-terminal-emulator first
+Author: Michael Stapelberg <stapelberg@debian.org>
+Origin: vendor
+Forwarded: not-needed
+Last-Update: 2012-09-19
+
+---
+
+Index: i3-4.3/i3-sensible-terminal
 ===================================================================
---- i3-4.1.1.orig/i3-sensible-terminal 2011-12-28 23:51:52.455610236 +0100
-+++ i3-4.1.1/i3-sensible-terminal      2011-12-28 23:52:00.826775027 +0100
+--- i3-4.3.orig/i3-sensible-terminal   2012-09-19 18:08:09.000000000 +0200
++++ i3-4.3/i3-sensible-terminal        2012-09-19 18:32:06.393883488 +0200
 @@ -4,11 +4,7 @@
  #
  # This script tries to exec a terminal emulator by trying some known terminal
@@ -10,8 +18,8 @@ Index: i3-4.1.1/i3-sensible-terminal
 -# Distributions/packagers should enhance this script with a
 -# distribution-specific mechanism to find the preferred terminal emulator. On
 -# Debian, there is the x-terminal-emulator symlink for example.
--for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
-+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do
+-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
++for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do
      if which $terminal > /dev/null 2>&1; then
          exec $terminal "$@"
      fi
index 6bae8165b42102cafac987fa6ecc07165830a1d9..011119d0188950ad4f868af90cfa89b7d8b6d150 100755 (executable)
@@ -38,7 +38,7 @@ override_dh_auto_build:
        $(MAKE) -C docs
 
 override_dh_installchangelogs:
-       dh_installchangelogs RELEASE-NOTES-4.2
+       dh_installchangelogs RELEASE-NOTES-4.3
 
 override_dh_install:
        $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install
index 21ba9aa0f8e350252e1fdbc8f7a8f76b9d125056..29ce571346985f499b1215bace0ea01f979547d2 100644 (file)
@@ -44,11 +44,16 @@ understand the old protocol version, but in order to use the new one, you need
 to provide the correct version. The header block is terminated by a newline and
 consists of a single JSON hash:
 
-*Example*:
+*Minimal example*:
 ----------------
 { "version": 1 }
 ----------------
 
+*All features example*:
+----------------
+{ "version": 1, "stop_signal": 10, "cont_signal": 12 }
+----------------
+
 (Note that before i3 v4.3 the precise format had to be +{"version":1}+,
 byte-for-byte.)
 
@@ -93,6 +98,19 @@ You can find an example of a shell script which can be used as your
 +status_command+ in the bar configuration at
 http://code.stapelberg.de/git/i3/tree/contrib/trivial-bar-script.sh?h=next
 
+=== Header in detail
+
+version::
+       The version number (as an integer) of the i3bar protocol you will use.
+stop_signal::
+       Specify to i3bar the signal (as an integer) to send to stop your
+       processing.
+       The default value (if none is specified) is SIGSTOP.
+cont_signal::
+       Specify to i3bar the signal (as an integer)to send to continue your
+       processing.
+       The default value (if none is specified) is SIGCONT.
+
 === Blocks in detail
 
 full_text::
index f8dfa78e4ca0fdb117f0dd4b79365cd98bdba850..a6666ef318e99944c1c586239eab7bf9503822be 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -274,6 +274,8 @@ name (string)::
 border (string)::
        Can be either "normal", "none" or "1pixel", dependending on the
        container’s border style.
+current_border_width (integer)::
+       Number of pixels of the border width.
 layout (string)::
        Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or
        "output".
@@ -610,6 +612,8 @@ workspace (0)::
 output (1)::
        Sent when RandR issues a change notification (of either screens,
        outputs, CRTCs or output properties).
+mode (2)::
+       Sent whenever i3 changes its binding mode.
 
 *Example:*
 --------------------------------------------------------------------
@@ -651,6 +655,18 @@ This event consists of a single serialized map containing a property
 { "change": "unspecified" }
 ---------------------------
 
+=== mode event
+
+This event consists of a single serialized map containing a property
++change (string)+ which holds the name of current mode in use. The name
+is the same as specified in config when creating a mode. The default
+mode is simply named default.
+
+*Example:*
+---------------------------
+{ "change": "default" }
+---------------------------
+
 == See also
 
 For some languages, libraries are available (so you don’t have to implement
index 2214f016522764598a3f7517cee285cf735c228e..cde7bfc4be2041807f1692493fd6da20cca8ed00 100644 (file)
@@ -483,14 +483,26 @@ This option determines which border style new windows will have. The default is
 
 *Syntax*:
 ---------------------------------------------
-new_window <normal|1pixel|none>
+new_window <normal|1pixel|none|pixel>
 ---------------------------------------------
-
 *Example*:
 ---------------------
 new_window 1pixel
 ---------------------
 
+The "normal" and "pixel" border styles support an optional border width in
+pixels:
+
+*Example*:
+---------------------
+# The same as new_window none
+new_window pixel 0
+
+# A 3 px border
+new_window pixel 3
+---------------------
+
+
 === Hiding vertical borders
 
 You can hide vertical borders adjacent to the screen edges using
@@ -874,6 +886,30 @@ workspace_auto_back_and_forth <yes|no>
 workspace_auto_back_and_forth yes
 ---------------------------------
 
+=== Delaying urgency hint reset on workspace change
+
+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
+may make it unnecessarily hard to tell which window originally raised the
+event.
+
+In order to prevent this, you can tell i3 to delay resetting the urgency state
+by a certain time using the +force_display_urgency_hint+ directive. Setting the
+value to 0 disables this feature.
+
+The default is 500ms.
+
+*Syntax*:
+---------------------------------------
+force_display_urgency_hint <timeout> ms
+---------------------------------------
+
+*Example*:
+---------------------------------
+force_display_urgency_hint 500 ms
+---------------------------------
+
 == Configuring i3bar
 
 The bar at the bottom of your monitor is drawn by a separate process called
@@ -1395,17 +1431,18 @@ RandR output.
 
 [[back_and_forth]]
 To switch back to the previously focused workspace, use +workspace
-back_and_forth+.
+back_and_forth+; likewise, you can move containers to the previously focused
+workspace using +move container to workspace back_and_forth+.
 
 *Syntax*:
 -----------------------------------
 workspace <next|prev|next_on_output|prev_on_output>
 workspace back_and_forth
 workspace <name>
-workspace number <number>
+workspace number <name>
 
 move [window|container] [to] workspace <name>
-move [window|container] [to] workspace number <number>
+move [window|container] [to] workspace number <name>
 move [window|container] [to] workspace <prev|next|current>
 -----------------------------------
 
@@ -1421,6 +1458,7 @@ bindsym mod+Shift+2 move container to workspace 2
 
 # switch between the current and the previously focused one
 bindsym mod+b workspace back_and_forth
+bindsym mod+Shift+b move container to workspace back_and_forth
 
 # move the whole workspace to the next output
 bindsym mod+x move workspace to output right
@@ -1456,7 +1494,8 @@ workspaces are ordered the way they appeared. When they start with a number, i3
 will order them numerically. Also, you will be able to use +workspace number 1+
 to switch to the workspace which begins with number 1, regardless of which name
 it has. This is useful in case you are changing the workspace’s name
-dynamically.
+dynamically. To combine both commands you can use +workspace number 1: mail+ to
+specify a default name if there's currently no workspace starting with a "1".
 
 ==== Renaming workspaces
 
@@ -1685,7 +1724,9 @@ invisible until you show it again. There is no way to open that workspace.
 Instead, when using +scratchpad show+, the window will be shown again, as a
 floating window, centered on your current workspace (using +scratchpad show+ on
 a visible scratchpad window will make it hidden again, so you can have a
-keybinding to toggle).
+keybinding to toggle). Note that this is just a normal floating window, so if
+you want to "remove it from scratchpad", you can simple make it tiling again
+(+floating toggle+).
 
 As the name indicates, this is useful for having a window with your favorite
 editor always at hand. However, you can also use this for other permanently
@@ -1818,6 +1859,8 @@ have more than one monitor:
 3. If you have many workspaces on many monitors, it might get hard to keep
    track of which window you put where. Thus, you can use vim-like marks to
    quickly switch between windows. See <<vim_like_marks>>.
+4. For information on how to move existing workspaces between monitors,
+   see <<_moving_containers_workspaces_to_randr_outputs>>.
 
 == i3 and the rest of your software world
 
index 4ed182c4af900ae450ef28e588a2423264395e74..372ed161c775461bf25cffe0535e774194de0eb1 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _XCB_H
-#define _XCB_H
+#ifndef I3_XCB_H
+#define I3_XCB_H
 
 /* from X11/keysymdef.h */
 #define XCB_NUM_LOCK                    0xff7f
index f494cbd56704c260c4806e486a17fd301948c4b8..f1d5f077f4cf421d184aae3f6fb897bba8a5fafd 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _I3_INPUT
-#define _I3_INPUT
+#ifndef I3_INPUT
+#define I3_INPUT
 
 #include <err.h>
 
index 5a21226bb532a8f9681bc328a7fd2342163a8c94..379a7f6f7e4b67a49f0a1c0652234447cc258bc1 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _I3_NAGBAR
-#define _I3_NAGBAR
+#ifndef I3_NAGBAR
+#define I3_NAGBAR
 
 #include <err.h>
 
index c0b56a013aba1fb1efee566ed6e583bf421591c9..d1c46890bc28e6534fdebf7987da54572c1e5bc0 100644 (file)
 #ifndef CHILD_H_
 #define CHILD_H_
 
+#include <stdbool.h>
+
 #define STDIN_CHUNK_SIZE 1024
 
+typedef struct {
+    pid_t pid;
+
+    /**
+     * The version number is an uint32_t to avoid machines with different sizes of
+     * 'int' to allow different values here. It’s highly unlikely we ever exceed
+     * even an int8_t, but still…
+     */
+    uint32_t version;
+
+    bool stopped;
+    /**
+     * The signal requested by the client to inform it of the hidden state of i3bar
+     */
+    int stop_signal;
+    /**
+     * The signal requested by the client to inform it of theun hidden state of i3bar
+     */
+    int cont_signal;
+} i3bar_child;
+
 /*
  * Start a child-process with the specified command and reroute stdin.
  * We actually start a $SHELL to execute the command so we don't have to care
index 6f8a7b2db8ae87b378e34ce19160e8a29c686ed6..e2582a02053bab03caacbc486774a47a27166cfa 100644 (file)
@@ -34,6 +34,8 @@ struct status_block {
 
     char *color;
 
+    bool urgent;
+
     /* The amount of pixels necessary to render this block. This variable is
      * only temporarily used in refresh_statusline(). */
     uint32_t width;
@@ -52,6 +54,6 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head;
 #include "xcb.h"
 #include "config.h"
 #include "libi3.h"
-#include "determine_json_version.h"
+#include "parse_json_header.h"
 
 #endif
diff --git a/i3bar/include/determine_json_version.h b/i3bar/include/determine_json_version.h
deleted file mode 100644 (file)
index 52c6f5d..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
- *
- * determine_json_version.c: Determines the JSON protocol version based on the
- *                           first line of input from a child program.
- *
- */
-#ifndef DETERMINE_JSON_VERSION_H_
-#define DETERMINE_JSON_VERSION_H_
-
-#include <stdint.h>
-
-/*
- * Determines the JSON i3bar protocol version from the given buffer. In case
- * the buffer does not contain valid JSON, or no version field is found, this
- * function returns -1. The amount of bytes consumed by parsing the header is
- * returned in *consumed (if non-NULL).
- *
- * The return type is an int32_t to avoid machines with different sizes of
- * 'int' to allow different values here. It’s highly unlikely we ever exceed
- * even an int8_t, but still…
- *
- */
-int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed);
-
-#endif
diff --git a/i3bar/include/parse_json_header.h b/i3bar/include/parse_json_header.h
new file mode 100644 (file)
index 0000000..79efddc
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * parse_json_header.c: Parse the JSON protocol header to determine
+ *                      protocol version and features.
+ *
+ */
+#ifndef PARSE_JSON_HEADER_H_
+#define PARSE_JSON_HEADER_H_
+
+#include <stdint.h>
+
+/**
+ * Parse the JSON protocol header to determine protocol version and features.
+ * In case the buffer does not contain a valid header (invalid JSON, or no
+ * version field found), the 'correct' field of the returned header is set to
+ * false. The amount of bytes consumed by parsing the header is returned in
+ * *consumed (if non-NULL).
+ *
+ */
+void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed);
+
+#endif
index 43c56c58d3e93794d6f56becc86ae128a73d84bb..6ae97815384049ec87d95ec94fd6788c19b648d4 100644 (file)
@@ -16,6 +16,8 @@
 #undef MIN
 #define MIN(x,y) ((x) < (y) ? (x) : (y))
 
+#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0)
+
 /* Securely free p */
 #define FREE(p) do { \
     if (p != NULL) { \
index 6c7bc567e4b07d7385667f7476cfa91980f2f922..dcc4d78116e9a6b1f9a4ca954acb251e2d65e153 100644 (file)
@@ -110,7 +110,7 @@ void reconfig_windows(void);
  * Render the bars, with buttons and statusline
  *
  */
-void draw_bars(void);
+void draw_bars(bool force_unhide);
 
 /*
  * Redraw the bars, i.e. simply copy the buffer to the barwindow
index 058ddb7a1f31c0518b20ae4afc6e38a6c65db85b..9a89d3c63eb608bf977138c04ecef25813d5b58d 100644 (file)
 #include "common.h"
 
 /* Global variables for child_*() */
-pid_t child_pid;
+i3bar_child child = { 0 };
 
 /* stdin- and sigchild-watchers */
 ev_io    *stdin_io;
 ev_child *child_sig;
 
 /* JSON parser for stdin */
-bool first_line = true;
-bool plaintext = false;
 yajl_callbacks callbacks;
 yajl_handle parser;
 
 typedef struct parser_ctx {
+    /* True if one of the parsed blocks was urgent */
+    bool has_urgent;
+
     /* A copy of the last JSON map key. */
     char *last_map_key;
 
@@ -69,6 +70,8 @@ void cleanup(void) {
         ev_child_stop(main_loop, child_sig);
         FREE(child_sig);
     }
+
+    memset(&child, 0, sizeof(i3bar_child));
 }
 
 /*
@@ -109,6 +112,14 @@ static int stdin_map_key(void *context, const unsigned char *key, unsigned int l
     return 1;
 }
 
+static int stdin_boolean(void *context, int val) {
+    parser_ctx *ctx = context;
+    if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
+        ctx->block.urgent = val;
+    }
+    return 1;
+}
+
 #if YAJL_MAJOR >= 2
 static int stdin_string(void *context, const unsigned char *val, size_t len) {
 #else
@@ -132,6 +143,8 @@ static int stdin_end_map(void *context) {
      * i3bar doesn’t crash and the user gets an annoying message. */
     if (!new_block->full_text)
         new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)");
+    if (new_block->urgent)
+        ctx->has_urgent = true;
     TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
     return 1;
 }
@@ -148,11 +161,10 @@ static int stdin_end_array(void *context) {
 }
 
 /*
- * Callbalk for stdin. We read a line from stdin and store the result
- * in statusline
+ * Helper function to read stdin
  *
  */
-void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
     int fd = watcher->fd;
     int n = 0;
     int rec = 0;
@@ -173,8 +185,9 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
             /* end of file, kill the watcher */
             ELOG("stdin: received EOF\n");
             cleanup();
-            draw_bars();
-            return;
+            draw_bars(false);
+            *ret_buffer_len = -1;
+            return NULL;
         }
         rec += n;
 
@@ -185,51 +198,94 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
     }
     if (*buffer == '\0') {
         FREE(buffer);
-        return;
+        rec = -1;
     }
+    *ret_buffer_len = rec;
+    return buffer;
+}
 
-    unsigned char *json_input = buffer;
-    if (first_line) {
-        DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
-        /* Detect whether this is JSON or plain text. */
-        unsigned int consumed = 0;
-        /* At the moment, we don’t care for the version. This might change
-         * in the future, but for now, we just discard it. */
-        plaintext = (determine_json_version(buffer, buffer_len, &consumed) == -1);
-        if (plaintext) {
-            /* In case of plaintext, we just add a single block and change its
-             * full_text pointer later. */
-            struct status_block *new_block = scalloc(sizeof(struct status_block));
-            TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
-        } else {
-            json_input += consumed;
-            rec -= consumed;
-        }
-        first_line = false;
-    }
-    if (!plaintext) {
-        yajl_status status = yajl_parse(parser, json_input, rec);
+static void read_flat_input(char *buffer, int length) {
+    struct status_block *first = TAILQ_FIRST(&statusline_head);
+    /* Clear the old buffer if any. */
+    I3STRING_FREE(first->full_text);
+    /* Remove the trailing newline and terminate the string at the same
+     * time. */
+    if (buffer[length-1] == '\n' || buffer[length-1] == '\r')
+        buffer[length-1] = '\0';
+    else buffer[length] = '\0';
+    first->full_text = i3string_from_utf8(buffer);
+}
+
+static bool read_json_input(unsigned char *input, int length) {
+    yajl_status status = yajl_parse(parser, input, length);
+    bool has_urgent = false;
 #if YAJL_MAJOR >= 2
-        if (status != yajl_status_ok) {
+    if (status != yajl_status_ok) {
 #else
-        if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
+    if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
 #endif
-            fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
-                    status, rec, json_input);
+        fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
+                status, length, input);
+    } else if (parser_context.has_urgent) {
+        has_urgent = true;
+    }
+    return has_urgent;
+}
+
+/*
+ * Callbalk for stdin. We read a line from stdin and store the result
+ * in statusline
+ *
+ */
+void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+    int rec;
+    unsigned char *buffer = get_buffer(watcher, &rec);
+    if (buffer == NULL)
+        return;
+    bool has_urgent = false;
+    if (child.version > 0) {
+        has_urgent = read_json_input(buffer, rec);
+    } else {
+        read_flat_input((char*)buffer, rec);
+    }
+    free(buffer);
+    draw_bars(has_urgent);
+}
+
+/*
+ * Callbalk for stdin first line. We read the first line to detect
+ * whether this is JSON or plain text
+ *
+ */
+void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
+    int rec;
+    unsigned char *buffer = get_buffer(watcher, &rec);
+    if (buffer == NULL)
+        return;
+    DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
+    /* Detect whether this is JSON or plain text. */
+    unsigned int consumed = 0;
+    /* At the moment, we don’t care for the version. This might change
+     * in the future, but for now, we just discard it. */
+    parse_json_header(&child, buffer, rec, &consumed);
+    if (child.version > 0) {
+        /* If hide-on-modifier is set, we start of by sending the
+         * child a SIGSTOP, because the bars aren't mapped at start */
+        if (config.hide_on_modifier) {
+            stop_child();
         }
+        read_json_input(buffer + consumed, rec - consumed);
     } else {
-        struct status_block *first = TAILQ_FIRST(&statusline_head);
-        /* Clear the old buffer if any. */
-        I3STRING_FREE(first->full_text);
-        /* Remove the trailing newline and terminate the string at the same
-         * time. */
-        if (buffer[rec-1] == '\n' || buffer[rec-1] == '\r')
-            buffer[rec-1] = '\0';
-        else buffer[rec] = '\0';
-        first->full_text = i3string_from_utf8((const char *)buffer);
+        /* In case of plaintext, we just add a single block and change its
+         * full_text pointer later. */
+        struct status_block *new_block = scalloc(sizeof(struct status_block));
+        TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
+        read_flat_input((char*)buffer, rec);
     }
     free(buffer);
-    draw_bars();
+    ev_io_stop(main_loop, stdin_io);
+    ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
+    ev_io_start(main_loop, stdin_io);
 }
 
 /*
@@ -240,7 +296,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
  */
 void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
     ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
-           child_pid,
+           child.pid,
            watcher->rstatus);
     cleanup();
 }
@@ -255,6 +311,7 @@ void start_child(char *command) {
     /* Allocate a yajl parser which will be used to parse stdin. */
     memset(&callbacks, '\0', sizeof(yajl_callbacks));
     callbacks.yajl_map_key = stdin_map_key;
+    callbacks.yajl_boolean = stdin_boolean;
     callbacks.yajl_string = stdin_string;
     callbacks.yajl_start_array = stdin_start_array;
     callbacks.yajl_end_array = stdin_end_array;
@@ -268,14 +325,13 @@ void start_child(char *command) {
     parser = yajl_alloc(&callbacks, NULL, &parser_context);
 #endif
 
-    child_pid = 0;
     if (command != NULL) {
         int fd[2];
         if (pipe(fd) == -1)
             err(EXIT_FAILURE, "pipe(fd)");
 
-        child_pid = fork();
-        switch (child_pid) {
+        child.pid = fork();
+        switch (child.pid) {
             case -1:
                 ELOG("Couldn't fork(): %s\n", strerror(errno));
                 exit(EXIT_FAILURE);
@@ -298,12 +354,6 @@ void start_child(char *command) {
 
                 dup2(fd[0], STDIN_FILENO);
 
-                /* If hide-on-modifier is set, we start of by sending the
-                 * child a SIGSTOP, because the bars aren't mapped at start */
-                if (config.hide_on_modifier) {
-                    stop_child();
-                }
-
                 break;
         }
     }
@@ -312,12 +362,12 @@ void start_child(char *command) {
     fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
 
     stdin_io = smalloc(sizeof(ev_io));
-    ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
+    ev_io_init(stdin_io, &stdin_io_first_line_cb, STDIN_FILENO, EV_READ);
     ev_io_start(main_loop, stdin_io);
 
     /* We must cleanup, if the child unexpectedly terminates */
     child_sig = smalloc(sizeof(ev_child));
-    ev_child_init(child_sig, &child_sig_cb, child_pid, 0);
+    ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
     ev_child_start(main_loop, child_sig);
 
     atexit(kill_child_at_exit);
@@ -328,9 +378,10 @@ void start_child(char *command) {
  *
  */
 void kill_child_at_exit(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGCONT);
-        kill(child_pid, SIGTERM);
+    if (child.pid > 0) {
+        if (child.cont_signal > 0 && child.stopped)
+            kill(child.pid, child.cont_signal);
+        kill(child.pid, SIGTERM);
     }
 }
 
@@ -340,12 +391,12 @@ void kill_child_at_exit(void) {
  *
  */
 void kill_child(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGCONT);
-        kill(child_pid, SIGTERM);
+    if (child.pid > 0) {
+        if (child.cont_signal > 0 && child.stopped)
+            kill(child.pid, child.cont_signal);
+        kill(child.pid, SIGTERM);
         int status;
-        waitpid(child_pid, &status, 0);
-        child_pid = 0;
+        waitpid(child.pid, &status, 0);
         cleanup();
     }
 }
@@ -355,8 +406,9 @@ void kill_child(void) {
  *
  */
 void stop_child(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGSTOP);
+    if (child.stop_signal > 0 && !child.stopped) {
+        child.stopped = true;
+        kill(child.pid, child.stop_signal);
     }
 }
 
@@ -365,7 +417,8 @@ void stop_child(void) {
  *
  */
 void cont_child(void) {
-    if (child_pid != 0) {
-        kill(child_pid, SIGCONT);
+    if (child.cont_signal > 0 && child.stopped) {
+        child.stopped = false;
+        kill(child.pid, child.cont_signal);
     }
 }
diff --git a/i3bar/src/determine_json_version.c b/i3bar/src/determine_json_version.c
deleted file mode 100644 (file)
index abd4303..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3bar - an xcb-based status- and ws-bar for i3
- * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
- *
- * determine_json_version.c: Determines the JSON protocol version based on the
- *                           first line of input from a child program.
- *
- */
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <err.h>
-#include <ev.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <yajl/yajl_common.h>
-#include <yajl/yajl_parse.h>
-#include <yajl/yajl_version.h>
-
-static bool version_key;
-static int32_t version_number;
-
-#if YAJL_MAJOR >= 2
-static int version_integer(void *ctx, long long val) {
-#else
-static int version_integer(void *ctx, long val) {
-#endif
-    if (version_key)
-        version_number = (uint32_t)val;
-    return 1;
-}
-
-#if YAJL_MAJOR >= 2
-static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
-#else
-static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
-#endif
-    version_key = (stringlen == strlen("version") &&
-                   strncmp((const char*)stringval, "version", strlen("version")) == 0);
-    return 1;
-}
-
-static yajl_callbacks version_callbacks = {
-    NULL, /* null */
-    NULL, /* boolean */
-    &version_integer,
-    NULL, /* double */
-    NULL, /* number */
-    NULL, /* string */
-    NULL, /* start_map */
-    &version_map_key,
-    NULL, /* end_map */
-    NULL, /* start_array */
-    NULL /* end_array */
-};
-
-/*
- * Determines the JSON i3bar protocol version from the given buffer. In case
- * the buffer does not contain valid JSON, or no version field is found, this
- * function returns -1. The amount of bytes consumed by parsing the header is
- * returned in *consumed (if non-NULL).
- *
- * The return type is an int32_t to avoid machines with different sizes of
- * 'int' to allow different values here. It’s highly unlikely we ever exceed
- * even an int8_t, but still…
- *
- */
-int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed) {
-#if YAJL_MAJOR >= 2
-    yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL);
-    /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
-     * yajl 2, we need to be explicit. */
-    yajl_config(handle, yajl_allow_trailing_garbage, 1);
-#else
-    yajl_parser_config parse_conf = { 0, 0 };
-
-    yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL);
-#endif
-
-    version_key = false;
-    version_number = -1;
-
-    yajl_status state = yajl_parse(handle, buffer, length);
-    if (state != yajl_status_ok) {
-        version_number = -1;
-        if (consumed != NULL)
-            *consumed = 0;
-    } else {
-        if (consumed != NULL)
-            *consumed = yajl_get_bytes_consumed(handle);
-    }
-
-    yajl_free(handle);
-
-    return version_number;
-}
index 2cc80cf7fac320cdf60af546e94331c6abe56d92..fc8c6492d9b9759211142aa7450740ddf1225e5d 100644 (file)
@@ -42,7 +42,7 @@ void got_command_reply(char *reply) {
 void got_workspace_reply(char *reply) {
     DLOG("Got Workspace-Data!\n");
     parse_workspaces_json(reply);
-    draw_bars();
+    draw_bars(false);
 }
 
 /*
@@ -71,7 +71,7 @@ void got_output_reply(char *reply) {
         kick_tray_clients(o_walk);
     }
 
-    draw_bars();
+    draw_bars(false);
 }
 
 /*
diff --git a/i3bar/src/parse_json_header.c b/i3bar/src/parse_json_header.c
new file mode 100644 (file)
index 0000000..80ec5af
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3bar - an xcb-based status- and ws-bar for i3
+ * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
+ *
+ * parse_json_header.c: Parse the JSON protocol header to determine
+ *                      protocol version and features.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <ev.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <yajl/yajl_common.h>
+#include <yajl/yajl_parse.h>
+#include <yajl/yajl_version.h>
+
+#include "common.h"
+
+static enum {
+    KEY_VERSION,
+    KEY_STOP_SIGNAL,
+    KEY_CONT_SIGNAL,
+    NO_KEY
+} current_key;
+
+#if YAJL_MAJOR >= 2
+static int header_integer(void *ctx, long long val) {
+#else
+static int header_integer(void *ctx, long val) {
+#endif
+    i3bar_child *child = ctx;
+
+    switch (current_key) {
+        case KEY_VERSION:
+            child->version = val;
+            break;
+        case KEY_STOP_SIGNAL:
+            child->stop_signal = val;
+            break;
+        case KEY_CONT_SIGNAL:
+            child->cont_signal = val;
+            break;
+        default:
+            break;
+    }
+    return 1;
+}
+
+#define CHECK_KEY(name) (stringlen == strlen(name) && \
+                         STARTS_WITH((const char*)stringval, stringlen, name))
+
+#if YAJL_MAJOR >= 2
+static int header_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
+#else
+static int header_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
+#endif
+    if (CHECK_KEY("version")) {
+        current_key = KEY_VERSION;
+    } else if (CHECK_KEY("stop_signal")) {
+        current_key = KEY_STOP_SIGNAL;
+    } else if (CHECK_KEY("cont_signal")) {
+        current_key = KEY_CONT_SIGNAL;
+    }
+    return 1;
+}
+
+static yajl_callbacks version_callbacks = {
+    NULL, /* null */
+    NULL, /* boolean */
+    &header_integer,
+    NULL, /* double */
+    NULL, /* number */
+    NULL, /* string */
+    NULL, /* start_map */
+    &header_map_key,
+    NULL, /* end_map */
+    NULL, /* start_array */
+    NULL /* end_array */
+};
+
+static void child_init(i3bar_child *child) {
+    child->version = 0;
+    child->stop_signal = SIGSTOP;
+    child->cont_signal = SIGCONT;
+}
+
+/*
+ * Parse the JSON protocol header to determine protocol version and features.
+ * In case the buffer does not contain a valid header (invalid JSON, or no
+ * version field found), the 'correct' field of the returned header is set to
+ * false. The amount of bytes consumed by parsing the header is returned in
+ * *consumed (if non-NULL).
+ *
+ */
+void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed) {
+    child_init(child);
+
+    current_key = NO_KEY;
+
+#if YAJL_MAJOR >= 2
+    yajl_handle handle = yajl_alloc(&version_callbacks, NULL, child);
+    /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
+     * yajl 2, we need to be explicit. */
+    yajl_config(handle, yajl_allow_trailing_garbage, 1);
+#else
+    yajl_parser_config parse_conf = { 0, 0 };
+
+    yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, child);
+#endif
+
+    yajl_status state = yajl_parse(handle, buffer, length);
+    if (state != yajl_status_ok) {
+        child_init(child);
+        if (consumed != NULL)
+            *consumed = 0;
+    } else {
+        if (consumed != NULL)
+            *consumed = yajl_get_bytes_consumed(handle);
+    }
+
+    yajl_free(handle);
+}
index 861925b96925f1aea7687dddd4eee8b97342c023..405eefdb0f6f58de196430a3ed28379a1c4decb7 100644 (file)
@@ -531,7 +531,7 @@ static void handle_client_message(xcb_client_message_event_t* event) {
             /* Trigger an update to copy the statusline text to the appropriate
              * position */
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
         }
     }
 }
@@ -559,7 +559,7 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) {
 
             /* Trigger an update, we now have more space for the statusline */
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
             return;
         }
     }
@@ -624,13 +624,13 @@ static void handle_property_notify(xcb_property_notify_event_t *event) {
             xcb_unmap_window(xcb_connection, trayclient->win);
             trayclient->mapped = map_it;
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
         } else if (!trayclient->mapped && map_it) {
             /* need to map the window */
             xcb_map_window(xcb_connection, trayclient->win);
             trayclient->mapped = map_it;
             configure_trayclients();
-            draw_bars();
+            draw_bars(false);
         }
         free(xembedr);
     }
@@ -1398,12 +1398,15 @@ void reconfig_windows(void) {
  * Render the bars, with buttons and statusline
  *
  */
-void draw_bars(void) {
+void draw_bars(bool unhide) {
     DLOG("Drawing Bars...\n");
     int i = 0;
 
     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) {
@@ -1460,8 +1463,6 @@ void draw_bars(void) {
         }
 
         i3_ws *ws_walk;
-        static char *last_urgent_ws = NULL;
-        bool has_urgent = false, walks_away = true;
 
         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);
@@ -1486,13 +1487,11 @@ void draw_bars(void) {
                 fg_color = colors.urgent_ws_fg;
                 bg_color = colors.urgent_ws_bg;
                 border_color = colors.urgent_ws_border;
-                has_urgent = true;
+                unhide = true;
                 if (!ws_walk->focused) {
                     FREE(last_urgent_ws);
                     last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name));
                 }
-                /* The urgent-hint should get noticed, so we unhide the bars shortly */
-                unhide_bars();
             }
             uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
             uint32_t vals_border[] = { border_color, border_color };
@@ -1522,12 +1521,17 @@ void draw_bars(void) {
             i += 10 + ws_walk->name_width + 1;
         }
 
-        if (!has_urgent && !mod_pressed && walks_away) {
+        i = 0;
+    }
+
+    if (!mod_pressed) {
+        if (unhide) {
+            /* The urgent-hint should get noticed, so we unhide the bars shortly */
+            unhide_bars();
+        } else if (walks_away) {
             FREE(last_urgent_ws);
             hide_bars();
         }
-
-        i = 0;
     }
 
     redraw_bars();
index b83b9f4e07df2c876a7c0e216a25dd5988822bae..48ca6621361e523ba93b78a6e6dd3b4cf5daf04f 100644 (file)
@@ -10,8 +10,8 @@
  * compile-time.
  *
  */
-#ifndef _ALL_H
-#define _ALL_H
+#ifndef I3_ALL_H
+#define I3_ALL_H
 
 #include <assert.h>
 #include <stdbool.h>
index f4ef8e88067afdb272fc1952d6101c272a37be2d..570375cf1874220a098f3212f9c0ac1faaecb6e9 100644 (file)
@@ -7,8 +7,8 @@
  * assignments.c: Assignments for specific windows (for_window).
  *
  */
-#ifndef _ASSIGNMENTS_H
-#define _ASSIGNMENTS_H
+#ifndef I3_ASSIGNMENTS_H
+#define I3_ASSIGNMENTS_H
 
 /**
  * Checks the list of assignments for the given window and runs all matching
index 6261613ba762c19dace78d9073f4f571d96a6f61..3c4d5288fdb8119a5e98086bdc1af15d1f625153 100644 (file)
@@ -7,8 +7,8 @@
  * click.c: Button press (mouse click) events.
  *
  */
-#ifndef _CLICK_H
-#define _CLICK_H
+#ifndef I3_CLICK_H
+#define I3_CLICK_H
 
 /**
  * The button press X callback. This function determines whether the floating
index d619b97c17932fe8966fb0b7f32c111b28df0926..4a87c39c8b266e73ca09002c3b1b4154d4738842 100644 (file)
@@ -7,8 +7,8 @@
  * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
  *
  */
-#ifndef _CMDPARSE_H
-#define _CMDPARSE_H
+#ifndef I3_CMDPARSE_H
+#define I3_CMDPARSE_H
 
 char *parse_cmd(const char *new);
 
index 37ee98d9c6d797886b1500b5110b4da7300605c7..6d195a092473cc1c7cd5f75d7ab0eca2aa24dea2 100644 (file)
@@ -7,8 +7,8 @@
  * commands.c: all command functions (see commands_parser.c)
  *
  */
-#ifndef _COMMANDS_H
-#define _COMMANDS_H
+#ifndef I3_COMMANDS_H
+#define I3_COMMANDS_H
 
 #include "commands_parser.h"
 
@@ -55,6 +55,12 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue);
  */
 void cmd_move_con_to_workspace(I3_CMD, char *which);
 
+/**
+ * Implementation of 'move [window|container] [to] workspace back_and_forth'.
+ *
+ */
+void cmd_move_con_to_workspace_back_and_forth(I3_CMD);
+
 /**
  * Implementation of 'move [window|container] [to] workspace <name>'.
  *
@@ -77,7 +83,7 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
  * Implementation of 'border normal|none|1pixel|toggle'.
  *
  */
-void cmd_border(I3_CMD, char *border_style_str);
+void cmd_border(I3_CMD, char *border_style_str, char *border_width);
 
 /**
  * Implementation of 'nop <comment>'.
index 795cb0265715c44a5d41319a561d161682dc490f..6ff8d54ea81a1ccdff2567fb3d9cc16ebec19d8d 100644 (file)
@@ -7,8 +7,8 @@
  * commands.c: all command functions (see commands_parser.c)
  *
  */
-#ifndef _COMMANDS_PARSER_H
-#define _COMMANDS_PARSER_H
+#ifndef I3_COMMANDS_PARSER_H
+#define I3_COMMANDS_PARSER_H
 
 #include <yajl/yajl_gen.h>
 
index 20e83df935bdf7501ec0da3a8c7918245d8f36a1..5bf82487ec0d6d2473603bec0f0b7932eda1a92f 100644 (file)
@@ -9,8 +9,8 @@
  *        …).
  *
  */
-#ifndef _CON_H
-#define _CON_H
+#ifndef I3_CON_H
+#define I3_CON_H
 
 /**
  * Create a new container (and attach it to the given parent, if not NULL).
@@ -66,6 +66,12 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation);
  */
 Con *con_get_fullscreen_con(Con *con, int fullscreen_mode);
 
+/**
+ * Returns true if the container is internal, such as __i3_scratch
+ *
+ */
+bool con_is_internal(Con *con);
+
 /**
  * Returns true if the node is floating.
  *
@@ -244,7 +250,7 @@ int con_border_style(Con *con);
  * floating window.
  *
  */
-void con_set_border_style(Con *con, int border_style);
+void con_set_border_style(Con *con, int border_style, int border_width);
 
 /**
  * This function changes the layout of a given container. Use it to handle
@@ -293,4 +299,23 @@ Rect con_minimum_size(Con *con);
  */
 bool con_fullscreen_permits_focusing(Con *con);
 
+/**
+ * Checks if the given container has an urgent child.
+ *
+ */
+bool con_has_urgent_child(Con *con);
+
+/**
+ * Make all parent containers urgent if con is urgent or clear the urgent flag
+ * of all parent containers if there are no more urgent children left.
+ *
+ */
+void con_update_parents_urgency(Con *con);
+
+/**
+ * Create a string representing the subtree under con.
+ *
+ */
+char *con_get_tree_representation(Con *con);
+
 #endif
index 1a48016a386ef635d8232afda17638ba112f867c..76fee94d9c97f716c5a984a9679dc208202f1988 100644 (file)
@@ -10,8 +10,8 @@
  * mode).
  *
  */
-#ifndef _CONFIG_H
-#define _CONFIG_H
+#ifndef I3_CONFIG_H
+#define I3_CONFIG_H
 
 #include <stdbool.h>
 #include "queue.h"
@@ -98,6 +98,7 @@ struct Config {
     int default_layout;
     int container_stack_limit;
     int container_stack_limit_value;
+    int default_border_width;
 
     /** Default orientation for new containers */
     int default_orientation;
@@ -149,6 +150,13 @@ struct Config {
      * between two workspaces. */
     bool workspace_auto_back_and_forth;
 
+    /** By default, urgency is cleared immediately when switching to another
+     * workspace leads to focusing the con with the urgency hint. When having
+     * multiple windows on that workspace, the user needs to guess which
+     * application raised the event. To prevent this, the reset of the urgency
+     * flag can be delayed using an urgency timer. */
+    float workspace_urgency_timer;
+
     /** The default border style for new windows. */
     border_style_t default_border;
 
index 02f781c9356e0e06fb3ec8eb5bd56e2c53f5551e..3cf22f61ead571e30bc0b1bc2362aeeff5e1b1d6 100644 (file)
@@ -7,8 +7,8 @@
  * include/data.h: This file defines all data structures used by i3
  *
  */
-#ifndef _DATA_H
-#define _DATA_H
+#ifndef I3_DATA_H
+#define I3_DATA_H
 
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-launcher.h>
@@ -55,7 +55,7 @@ typedef struct Window i3Window;
  *****************************************************************************/
 typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t;
 typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t;
-typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t;
+typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t;
 
 /** parameter to specify whether tree_close() and x_window_kill() should kill
  * only this specific window or the whole X11 client */
@@ -485,6 +485,7 @@ struct Con {
 
     /* the x11 border pixel attribute */
     int border_width;
+    int current_border_width;
 
     /* minimum increment size specified for the window (in pixels) */
     int width_increment;
@@ -496,6 +497,9 @@ struct Con {
      * inside this container (if any) sets the urgency hint, for example. */
     bool urgent;
 
+    /* timer used for disabling urgency */
+    struct ev_timer *urgency_timer;
+
     /* ids/pixmap/graphics context for the frame window */
     xcb_window_t frame;
     xcb_pixmap_t pixmap;
index abf9c76dfe0b6692032c006312c2f3599bd0aefc..44c95c6d348627d9d9305e52360dd183d11d3ead 100644 (file)
@@ -8,8 +8,8 @@
  *          events.  This code is from xcb-util.
  *
  */
-#ifndef _DEBUG_H
-#define _DEBUG_H
+#ifndef I3_DEBUG_H
+#define I3_DEBUG_H
 
 int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e);
 
index 97b3902c95652fa82c575d487eb50d590f11c22d..88a1abc1e9cc1f8013d64d0373a927cc35d51896 100644 (file)
@@ -7,8 +7,8 @@
  * display_version.c: displays the running i3 version, runs as part of
  *                    i3 --moreversion.
  */
-#ifndef _DISPLAY_VERSION_H
-#define _DISPLAY_VERSION_H
+#ifndef I3_DISPLAY_VERSION_H
+#define I3_DISPLAY_VERSION_H
 
 /**
  * Connects to i3 to find out the currently running version. Useful since it
index a786069a337dc9cb8171d00a6ef160231a771e92..07ef6614a420bf4393202164bcc2b4cf3b5ae884 100644 (file)
@@ -7,8 +7,8 @@
  * ewmh.c: Get/set certain EWMH properties easily.
  *
  */
-#ifndef _EWMH_C
-#define _EWMH_C
+#ifndef I3_EWMH_C
+#define I3_EWMH_C
 
 /**
  * Updates _NET_CURRENT_DESKTOP with the current desktop number.
index adb10a0d2c448095f8d3c788d43f14b3a556dfbf..bfeba292470af38e800a553eec644becf3385a2f 100644 (file)
@@ -8,8 +8,8 @@
  * which don’t support multi-monitor in a useful way) and for our testsuite.
  *
  */
-#ifndef _FAKE_OUTPUTS_H
-#define _FAKE_OUTPUTS_H
+#ifndef I3_FAKE_OUTPUTS_H
+#define I3_FAKE_OUTPUTS_H
 
 /**
  * Creates outputs according to the given specification.
index 43137c9c7ed47ca174b850b1f54296760b2050b2..884d3cf1fe9df848a5ffdb2f2b9dd8f4e70de1b9 100644 (file)
@@ -7,8 +7,8 @@
  * floating.c: Floating windows.
  *
  */
-#ifndef _FLOATING_H
-#define _FLOATING_H
+#ifndef I3_FLOATING_H
+#define I3_FLOATING_H
 
 #include "tree.h"
 
@@ -134,8 +134,8 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
  *
  */
 void drag_pointer(Con *con, const xcb_button_press_event_t *event,
-                  xcb_window_t confine_to, border_t border, callback_t callback,
-                  const void *extra);
+                  xcb_window_t confine_to, border_t border, int cursor,
+                  callback_t callback, const void *extra);
 
 /**
  * Repositions the CT_FLOATING_CON to have the coordinates specified by
index bcebf9ff0901e38cff023f484a335ca517ad71c2..b2e7ce2e9288c3751486c22d3f4964734f351297 100644 (file)
@@ -8,8 +8,8 @@
  *             …).
  *
  */
-#ifndef _HANDLERS_H
-#define _HANDLERS_H
+#ifndef I3_HANDLERS_H
+#define I3_HANDLERS_H
 
 #include <xcb/randr.h>
 
index bd40f16b9eb8e79937d562937fab9fc743394a90..1bc8b55da13bd81a3b5aa6f93a1ee84c98a69016 100644 (file)
@@ -7,8 +7,8 @@
  * i3.h: global variables that are used all over i3.
  *
  */
-#ifndef _I3_H
-#define _I3_H
+#ifndef I3_I3_H
+#define I3_I3_H
 
 #include <sys/time.h>
 #include <sys/resource.h>
index 0906b7f919bbb98d1e21598557aec29b00b2ad7b..93b2ae87392e43a43b000ae5cd9d0beeed83c6c6 100644 (file)
@@ -8,8 +8,8 @@
  * for the IPC interface to i3 (see docs/ipc for more information).
  *
  */
-#ifndef _I3_IPC_H
-#define _I3_IPC_H
+#ifndef I3_I3_IPC_H
+#define I3_I3_IPC_H
 
 /*
  * Messages from clients to i3
@@ -84,4 +84,7 @@
 /* The output event will be triggered upon changes in the output list */
 #define I3_IPC_EVENT_OUTPUT                     (I3_IPC_EVENT_MASK | 1)
 
+/* The output event will be triggered upon mode changes */
+#define I3_IPC_EVENT_MODE                       (I3_IPC_EVENT_MASK | 2)
+
 #endif
index af80fa4bd18116bc071cf87f360bd577d850bc8c..ef50ba8630da305dd977d7d2a7482d313bdae7da 100644 (file)
@@ -7,8 +7,8 @@
  * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
  *
  */
-#ifndef _IPC_H
-#define _IPC_H
+#ifndef I3_IPC_H
+#define I3_IPC_H
 
 #include <ev.h>
 #include <stdbool.h>
index 4d469babe8bb2869ffede445d8efc5163c625e8e..417843a10fbea0c1a5e7c202539d7811e53f36e3 100644 (file)
@@ -7,8 +7,8 @@
  * key_press.c: key press handler
  *
  */
-#ifndef _KEY_PRESS_H
-#define _KEY_PRESS_H
+#ifndef I3_KEY_PRESS_H
+#define I3_KEY_PRESS_H
 
 /**
  * There was a key press. We compare this key code with our bindings table and pass
index d4df901fe4cde7400a895d4d2e6d585f93590a92..7547845b9bf2245a97242badd9bcd6d7c9ff8e7f 100644 (file)
@@ -8,8 +8,8 @@
  * as i3-msg, i3-config-wizard, …
  *
  */
-#ifndef _LIBI3_H
-#define _LIBI3_H
+#ifndef I3_LIBI3_H
+#define I3_LIBI3_H
 
 #include <stdbool.h>
 #include <stdarg.h>
index a2cd6d149d53f717ecdeb16ebbe3ee23171661f8..282512b2454e1be458e40d41538a2473817dd6d4 100644 (file)
@@ -8,8 +8,8 @@
  *                restart.
  *
  */
-#ifndef _LOAD_LAYOUT_H
-#define _LOAD_LAYOUT_H
+#ifndef I3_LOAD_LAYOUT_H
+#define I3_LOAD_LAYOUT_H
 
 void tree_append_json(const char *filename);
 
index 7822fba50f66b9ba51943e2eef62776bfc3e799d..6fabeca339fdd3283a35e039a6fd2c7922f1e697 100644 (file)
@@ -7,8 +7,8 @@
  * log.c: Logging functions.
  *
  */
-#ifndef _LOG_H
-#define _LOG_H
+#ifndef I3_LOG_H
+#define I3_LOG_H
 
 #include <stdarg.h>
 #include <stdbool.h>
index c16d295f6c3eabd53b0604a2fd1c81a39c80962a..d50f64d49b67efa9a7f538025edd2f5c09b1100e 100644 (file)
@@ -7,8 +7,8 @@
  * manage.c: Initially managing new windows (or existing ones on restart).
  *
  */
-#ifndef _MANAGE_H
-#define _MANAGE_H
+#ifndef I3_MANAGE_H
+#define I3_MANAGE_H
 
 #include "data.h"
 
index 6d9cb91539f4219cbfd5fc8ed9081dca2e5591db..e1d259040dfddbd10546bec6469e8df7a7543ee3 100644 (file)
@@ -11,8 +11,8 @@
  * match_matches_window() to find the windows affected by this command.
  *
  */
-#ifndef _MATCH_H
-#define _MATCH_H
+#ifndef I3_MATCH_H
+#define I3_MATCH_H
 
 /*
  * Initializes the Match data structure. This function is necessary because the
index 22b6e809ef05b558a475ea8a7bfa5fb476a6e015..d45e676e4d52b5e8b8b7c6cef919f17321dea2d9 100644 (file)
@@ -7,8 +7,8 @@
  * move.c: Moving containers into some direction.
  *
  */
-#ifndef _MOVE_H
-#define _MOVE_H
+#ifndef I3_MOVE_H
+#define I3_MOVE_H
 
 /**
  * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT,
index d488ad30c4baef8b67c7654bd4da0ddccffead3b..e87da22e063b1314bffebb349b6764a079006013 100644 (file)
@@ -7,8 +7,8 @@
  * output.c: Output (monitor) related functions.
  *
  */
-#ifndef _OUTPUT_H
-#define _OUTPUT_H
+#ifndef I3_OUTPUT_H
+#define I3_OUTPUT_H
 
 /**
  * Returns the output container below the given output container.
index ac527bcc9cc86feb45336686dbd63959646a4a05..b5c02144dc729986d3ecebb502ac2517af33706d 100644 (file)
@@ -9,8 +9,8 @@
  * (take your time to read it completely, it answers all questions).
  *
  */
-#ifndef _RANDR_H
-#define _RANDR_H
+#ifndef I3_RANDR_H
+#define I3_RANDR_H
 
 #include "data.h"
 #include <xcb/randr.h>
 TAILQ_HEAD(outputs_head, xoutput);
 extern struct outputs_head outputs;
 
+typedef enum {
+    CLOSEST_OUTPUT = 0,
+    FARTHEST_OUTPUT = 1
+} output_close_far_t;
+
 /**
  * We have just established a connection to the X server and need the initial
  * XRandR information to setup workspaces for each screen.
@@ -96,6 +101,6 @@ Output *get_output_most(direction_t direction, Output *current);
  * Gets the output which is the next one in the given direction.
  *
  */
-Output *get_output_next(direction_t direction, Output *current);
+Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far);
 
 #endif
index fe1e9f9537f47dcb13d029618763362c2048d054..7403abefb9dda55cf5431fe20c9c5d9f0602775e 100644 (file)
@@ -7,8 +7,8 @@
  * regex.c: Interface to libPCRE (perl compatible regular expressions).
  *
  */
-#ifndef _REGEX_H
-#define _REGEX_H
+#ifndef I3_REGEX_H
+#define I3_REGEX_H
 
 /**
  * Creates a new 'regex' struct containing the given pattern and a PCRE
index 1f31fb0f962e2bbc367dbd1240bbf3307ecd0e9d..0a5949f9fab35dc029b076cdd3d1407f38edac2d 100644 (file)
@@ -8,8 +8,8 @@
  *           various rects. Needs to be pushed to X11 (see x.c) to be visible.
  *
  */
-#ifndef _RENDER_H
-#define _RENDER_H
+#ifndef I3_RENDER_H
+#define I3_RENDER_H
 
 /**
  * "Renders" the given container (and its children), meaning that all rects are
index 99646ea087a857b07d6412e24ec72dd74f6cf4d3..fa0216c88ac3597e45193a32d7b1376e874d9860 100644 (file)
@@ -7,8 +7,8 @@
  * resize.c: Interactive resizing.
  *
  */
-#ifndef _RESIZE_H
-#define _RESIZE_H
+#ifndef I3_RESIZE_H
+#define I3_RESIZE_H
 
 int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event);
 
index 4d5533273983b1a8a11b8bc6d357fcdbd569fac2..c6157052ff5c9017d38b057137ba91670ddfa125 100644 (file)
@@ -7,8 +7,8 @@
  * scratchpad.c: Scratchpad functions (TODO: more description)
  *
  */
-#ifndef _SCRATCHPAD_H
-#define _SCRATCHPAD_H
+#ifndef I3_SCRATCHPAD_H
+#define I3_SCRATCHPAD_H
 
 /**
  * Moves the specified window to the __i3_scratch workspace, making it floating
index e755d2f139cdc003e9d5fc655d7aec7c882bd79a..fd3f53ebfac73a0ef60ea9f688c9126498626140 100644 (file)
@@ -8,8 +8,8 @@
  * default (ringbuffer for storing the debug log).
  *
  */
-#ifndef _I3_SHMLOG_H
-#define _I3_SHMLOG_H
+#ifndef I3_I3_SHMLOG_H
+#define I3_I3_SHMLOG_H
 
 #include <stdint.h>
 #include <pthread.h>
index 571070b04230079a1ae606400d1346ae8bb688fc..25d3385bc1e1cfec27e1f5bcd053966ccba6184e 100644 (file)
@@ -9,8 +9,8 @@
  *               to restart inplace).
  *
  */
-#ifndef _SIGHANDLER_H
-#define _SIGHANDLER_H
+#ifndef I3_SIGHANDLER_H
+#define I3_SIGHANDLER_H
 
 /**
  * Setup signal handlers to safely handle SIGSEGV and SIGFPE
index 290c8d21cf4f72db53d72dc7650fa5c0ea6c1414..bcc59a0a67104e34d30e45f481884354e66bc994 100644 (file)
@@ -10,8 +10,8 @@
  *            the appropriate workspace.
  *
  */
-#ifndef _STARTUP_H
-#define _STARTUP_H
+#ifndef I3_STARTUP_H
+#define I3_STARTUP_H
 
 #define SN_API_NOT_YET_FROZEN 1
 #include <libsn/sn-monitor.h>
index 8816b19a640a55d0fa84300bdd59295c56c24943..2799afee5fdf452b179dd8a597d3a539f40be929 100644 (file)
@@ -7,8 +7,8 @@
  * tree.c: Everything that primarily modifies the layout tree data structure.
  *
  */
-#ifndef _TREE_H
-#define _TREE_H
+#ifndef I3_TREE_H
+#define I3_TREE_H
 
 extern Con *croot;
 /* TODO: i am not sure yet how much access to the focused container should
index cd88863c282a90b976bd9ba02fb6b4c3c58ef3ca..e397a4e80ac37bbc77e230c6b497f12b3f33b638 100644 (file)
@@ -8,8 +8,8 @@
  *         also libi3).
  *
  */
-#ifndef _UTIL_H
-#define _UTIL_H
+#ifndef I3_UTIL_H
+#define I3_UTIL_H
 
 #include <err.h>
 
index 60198b878f3517e04addc9c33744d4e06ef16d49..59d3b1bbd97a3bc1401b6bda614bb23c89b6b11b 100644 (file)
@@ -7,8 +7,8 @@
  * window.c: Updates window attributes (X11 hints/properties).
  *
  */
-#ifndef _WINDOW_H
-#define _WINDOW_H
+#ifndef I3_WINDOW_H
+#define I3_WINDOW_H
 
 /**
  * Updates the WM_CLASS (consisting of the class and instance) for the
index 1b25b425f4d21b5e4fe0f5e5ab08fd34174f23bf..a7f2d13bd1ba47932f466b1aba5d41926543134e 100644 (file)
@@ -8,8 +8,8 @@
  *              workspaces.
  *
  */
-#ifndef _WORKSPACE_H
-#define _WORKSPACE_H
+#ifndef I3_WORKSPACE_H
+#define I3_WORKSPACE_H
 
 #include "data.h"
 #include "tree.h"
@@ -95,6 +95,12 @@ Con* workspace_prev_on_output(void);
  */
 void workspace_back_and_forth(void);
 
+/**
+ * Returns the previously focused workspace con, or NULL if unavailable.
+ *
+ */
+Con *workspace_back_and_forth_get(void);
+
 
 #if 0
 /**
index cb4a8a964d32823ab74dd1161c1469648ba5ea38..c3d4ffc7cc29f5bac0194aa9ca401f469ee70e8e 100644 (file)
@@ -8,8 +8,8 @@
  *      render.c). Basically a big state machine.
  *
  */
-#ifndef _X_H
-#define _X_H
+#ifndef I3_X_H
+#define I3_X_H
 
 /** Stores the X11 window ID of the currently focused window */
 extern xcb_window_t focused_id;
index 269038daef0926885056185f49005cbabcc61a0d..15d3e28fc4e1e81dbd8020fcd960ce6ba9b3f739 100644 (file)
@@ -7,8 +7,8 @@
  * xcb.c: Helper functions for easier usage of XCB
  *
  */
-#ifndef _XCB_H
-#define _XCB_H
+#ifndef I3_XCB_H
+#define I3_XCB_H
 
 #include "data.h"
 #include "xcursor.h"
index 9c2660b72b220d67d4e2d7a8d22a7ad3301e96b9..fc09a254323953afb13a7ed76147e0e988f6d719 100644 (file)
@@ -9,8 +9,8 @@
  *               older versions.
  *
  */
-#ifndef _XCB_COMPAT_H
-#define _XCB_COMPAT_H
+#ifndef I3_XCB_COMPAT_H
+#define I3_XCB_COMPAT_H
 
 #define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t
 #define xcb_icccm_get_wm_protocols xcb_get_wm_protocols
index c341f90ba6b77e6a2bfc028abb13528e2cc96b00..bfe37c3944cf098e22b91aa461cb92e7db5dde5c 100644 (file)
@@ -7,8 +7,8 @@
  * xcursor.c: libXcursor support for themed cursors.
  *
  */
-#ifndef _XCURSOR_CURSOR_H
-#define _XCURSOR_CURSOR_H
+#ifndef I3_XCURSOR_CURSOR_H
+#define I3_XCURSOR_CURSOR_H
 
 #include <X11/Xlib.h>
 
@@ -16,7 +16,12 @@ enum xcursor_cursor_t {
     XCURSOR_CURSOR_POINTER = 0,
     XCURSOR_CURSOR_RESIZE_HORIZONTAL,
     XCURSOR_CURSOR_RESIZE_VERTICAL,
+    XCURSOR_CURSOR_TOP_LEFT_CORNER,
+    XCURSOR_CURSOR_TOP_RIGHT_CORNER,
+    XCURSOR_CURSOR_BOTTOM_LEFT_CORNER,
+    XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER,
     XCURSOR_CURSOR_WATCH,
+    XCURSOR_CURSOR_MOVE,
     XCURSOR_CURSOR_MAX
 };
 
index 8c879c07fb7329c38b0c7fe8b2c5419a557d6cd7..ca7c2ab538a3938b42a6efdaaabc1bcb0833b047 100644 (file)
@@ -9,8 +9,8 @@
  * driver which does not support RandR in 2011 *sigh*.
  *
  */
-#ifndef _XINERAMA_H
-#define _XINERAMA_H
+#ifndef I3_XINERAMA_H
+#define I3_XINERAMA_H
 
 #include "data.h"
 
index b4c9e005bd8ab16583bae3ef2dc8aeab8984f1f9..4224707c4c8829122d6c0368bcb583f7cc61f8f6 100644 (file)
@@ -61,10 +61,20 @@ state EXEC:
   command = string
       -> call cmd_exec($nosn, $command)
 
-# border normal|none|1pixel|toggle
+# border normal|none|1pixel|toggle|1pixel
 state BORDER:
-  border_style = 'normal', 'none', '1pixel', 'toggle'
-      -> call cmd_border($border_style)
+  border_style = 'normal', 'pixel'
+    -> BORDER_WIDTH
+  border_style = 'none', 'toggle'
+    -> call cmd_border($border_style, "0")
+  border_style = '1pixel'
+    -> call cmd_border($border_style, "1")
+
+state BORDER_WIDTH:
+  end
+    -> call cmd_border($border_style, "2")
+  border_width = word
+    -> call cmd_border($border_style, $border_width)
 
 # layout default|stacked|stacking|tabbed|splitv|splith
 # layout toggle [split|all]
@@ -243,6 +253,8 @@ state MOVE_WORKSPACE:
       -> MOVE_WORKSPACE_TO_OUTPUT
   workspace = 'next', 'prev', 'next_on_output', 'prev_on_output', 'current'
       -> call cmd_move_con_to_workspace($workspace)
+  'back_and_forth'
+      -> call cmd_move_con_to_workspace_back_and_forth()
   'number'
       -> MOVE_WORKSPACE_NUMBER
   workspace = string
index 8ee2a1dad75fdcb5f414b59437c9f46c9662e815..6eef8a5ae6920d903a8ade3243ff5037b5fb8f9e 100644 (file)
@@ -56,6 +56,7 @@ EOL     (\r?\n)
 %s OUTPUT_COND
 %s FOR_WINDOW_COND
 %s EAT_WHITESPACE
+%s BORDER_WIDTH
 
 %x BUFFER_LINE
 %x BAR
@@ -171,6 +172,7 @@ EOL     (\r?\n)
                                 }
 <ASSIGN_TARGET_COND>[ \t]*→[ \t]*     { BEGIN(WANT_STRING); }
 <ASSIGN_TARGET_COND>[ \t]+      { BEGIN(WANT_STRING); }
+<BORDER_WIDTH>[^\n][0-9]+       { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;}
 <EXEC>--no-startup-id           { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
 <EXEC>.                         { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
 <OPTRELEASE>--release           { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; }
@@ -200,9 +202,10 @@ auto                            { return TOK_AUTO; }
 workspace_layout                { return TOK_WORKSPACE_LAYOUT; }
 new_window                      { return TOKNEWWINDOW; }
 new_float                       { return TOKNEWFLOAT; }
-normal                          { return TOK_NORMAL; }
+normal                          { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; }
 none                            { return TOK_NONE; }
 1pixel                          { return TOK_1PIXEL; }
+pixel                           { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; }
 hide_edge_borders               { return TOK_HIDE_EDGE_BORDERS; }
 both                            { return TOK_BOTH; }
 focus_follows_mouse             { return TOKFOCUSFOLLOWSMOUSE; }
@@ -212,6 +215,8 @@ force-xinerama                  { return TOK_FORCE_XINERAMA; }
 fake_outputs                    { WS_STRING; return TOK_FAKE_OUTPUTS; }
 fake-outputs                    { WS_STRING; return TOK_FAKE_OUTPUTS; }
 workspace_auto_back_and_forth   { return TOK_WORKSPACE_AUTO_BAF; }
+force_display_urgency_hint      { return TOK_WORKSPACE_URGENCY_TIMER; }
+ms                              { return TOK_TIME_MS; }
 workspace_bar                   { return TOKWORKSPACEBAR; }
 popup_during_fullscreen         { return TOK_POPUP_DURING_FULLSCREEN; }
 ignore                          { return TOK_IGNORE; }
index 29c519f0469f3aa6544993cb5701a9a8499e5759..8bc7990ea9c5512ddb8cd7653ca503e6c8fabfec 100644 (file)
@@ -108,6 +108,7 @@ static int detect_version(char *buf) {
                 strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
                 strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
                 strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
+                strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
                 strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
                 strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
                 strncasecmp(bind, "bar", strlen("bar")) == 0) {
@@ -728,6 +729,7 @@ void parse_file(const char *f) {
 %token  <color>         TOKCOLOR
 %token                  TOKARROW                    "→"
 %token                  TOKMODE                     "mode"
+%token                  TOK_TIME_MS                 "ms"
 %token                  TOK_BAR                     "bar"
 %token                  TOK_ORIENTATION             "default_orientation"
 %token                  TOK_HORIZ                   "horizontal"
@@ -738,6 +740,7 @@ void parse_file(const char *f) {
 %token                  TOKNEWFLOAT                 "new_float"
 %token                  TOK_NORMAL                  "normal"
 %token                  TOK_NONE                    "none"
+%token                  TOK_PIXEL                   "pixel"
 %token                  TOK_1PIXEL                  "1pixel"
 %token                  TOK_HIDE_EDGE_BORDERS       "hide_edge_borders"
 %token                  TOK_BOTH                    "both"
@@ -746,6 +749,7 @@ void parse_file(const char *f) {
 %token                  TOK_FORCE_XINERAMA          "force_xinerama"
 %token                  TOK_FAKE_OUTPUTS            "fake_outputs"
 %token                  TOK_WORKSPACE_AUTO_BAF      "workspace_auto_back_and_forth"
+%token                  TOK_WORKSPACE_URGENCY_TIMER "force_display_urgency_hint"
 %token                  TOKWORKSPACEBAR             "workspace_bar"
 %token                  TOK_DEFAULT                 "default"
 %token                  TOK_STACKING                "stacking"
@@ -816,9 +820,11 @@ void parse_file(const char *f) {
 %type   <number>        bar_mode_mode
 %type   <number>        bar_modifier_modifier
 %type   <number>        optional_no_startup_id
+%type   <number>        optional_border_width
 %type   <number>        optional_release
 %type   <string>        command
 %type   <string>        word_or_number
+%type   <string>        duration
 %type   <string>        qstring_or_number
 %type   <string>        optional_workspace_name
 %type   <string>        workspace_name
@@ -848,6 +854,7 @@ line:
     | force_focus_wrapping
     | force_xinerama
     | fake_outputs
+    | force_display_urgency_hint
     | workspace_back_and_forth
     | workspace_bar
     | workspace
@@ -1052,6 +1059,11 @@ word_or_number:
     }
     ;
 
+duration:
+    NUMBER { sasprintf(&$$, "%d", $1); }
+    | NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); }
+    ;
+
 mode:
     TOKMODE QUOTEDSTRING '{' modelines '}'
     {
@@ -1471,9 +1483,26 @@ new_float:
     ;
 
 border_style:
-    TOK_NORMAL      { $$ = BS_NORMAL; }
-    | TOK_NONE      { $$ = BS_NONE; }
-    | TOK_1PIXEL    { $$ = BS_1PIXEL; }
+    TOK_NORMAL optional_border_width
+    {
+        config.default_border_width = $2;
+        $$ = BS_NORMAL;
+    }
+    | TOK_1PIXEL
+    {
+        config.default_border_width = 1;
+        $$ = BS_PIXEL;
+    }
+    | TOK_NONE
+    {
+        config.default_border_width = 0;
+        $$ = BS_NONE;
+    }
+    | TOK_PIXEL optional_border_width
+    {
+        config.default_border_width = $2;
+        $$ = BS_PIXEL;
+    }
     ;
 
 bool:
@@ -1548,6 +1577,14 @@ workspace_back_and_forth:
     }
     ;
 
+force_display_urgency_hint:
+    TOK_WORKSPACE_URGENCY_TIMER duration
+    {
+        DLOG("workspace urgency_timer = %f\n", atoi($2) / 1000.0);
+        config.workspace_urgency_timer = atoi($2) / 1000.0;
+    }
+    ;
+
 workspace_bar:
     TOKWORKSPACEBAR bool
     {
@@ -1736,6 +1773,11 @@ exec_always:
     }
     ;
 
+optional_border_width:
+    /* empty */ { $$ = 2; } // 2 pixels is the default value for any type of border
+    | NUMBER  { $$ = $1; }
+    ;
+
 optional_no_startup_id:
     /* empty */ { $$ = false; }
     | TOK_NO_STARTUP_ID  { $$ = true; }
index 23b6be4f15483d417101221f8f7a778c9cf0c2c5..a1da00ac90296758e4deafa32ca8be1bc0467da5 100644 (file)
@@ -309,6 +309,25 @@ int handle_button_press(xcb_button_press_event_t *event) {
         return route_click(con, event, mod_pressed, CLICK_INSIDE);
 
     if (!(con = con_by_frame_id(event->event))) {
+        /* If the root window is clicked, find the relevant output from the
+         * click coordinates and focus the output's active workspace. */
+        if (event->event == root) {
+            Con *output, *ws;
+            TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+                if (con_is_internal(output) ||
+                    !rect_contains(output->rect, event->event_x, event->event_y))
+                    continue;
+
+                ws = TAILQ_FIRST(&(output_get_content(output)->focus_head));
+                if (ws != con_get_workspace(focused)) {
+                    workspace_show(ws);
+                    tree_render();
+                }
+                return 1;
+            }
+            return 0;
+        }
+
         ELOG("Clicked into unknown window?!\n");
         xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
         xcb_flush(conn);
index 2d8fce3cc410cf7802eb2a72c280cdbc1b238553..607e1c11dbaae0b42ba4a75b6eb7d43d61a1e174 100644 (file)
@@ -57,19 +57,19 @@ static Output *get_output_from_string(Output *current_output, const char *output
     Output *output;
 
     if (strcasecmp(output_str, "left") == 0) {
-        output = get_output_next(D_LEFT, current_output);
+        output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_RIGHT, current_output);
     } else if (strcasecmp(output_str, "right") == 0) {
-        output = get_output_next(D_RIGHT, current_output);
+        output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_LEFT, current_output);
     } else if (strcasecmp(output_str, "up") == 0) {
-        output = get_output_next(D_UP, current_output);
+        output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_DOWN, current_output);
     } else if (strcasecmp(output_str, "down") == 0) {
-        output = get_output_next(D_DOWN, current_output);
+        output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
         if (!output)
             output = get_output_most(D_UP, current_output);
     } else output = get_output_by_name(output_str);
@@ -99,6 +99,29 @@ static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
     return true;
 }
 
+/*
+ * Return the passed workspace unless it is the current one and auto back and
+ * forth is enabled, in which case the back_and_forth workspace is returned.
+ */
+static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
+    Con *current, *baf;
+
+    if (!config.workspace_auto_back_and_forth)
+        return workspace;
+
+    current = con_get_workspace(focused);
+
+    if (current == workspace) {
+        baf = workspace_back_and_forth_get();
+        if (baf != NULL) {
+            DLOG("Substituting workspace with back_and_forth, as it is focused.\n");
+            return baf;
+        }
+    }
+
+    return workspace;
+}
+
 // This code is commented out because we might recycle it for popping up error
 // messages on parser errors.
 #if 0
@@ -400,6 +423,38 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) {
     ysuccess(true);
 }
 
+/**
+ * Implementation of 'move [window|container] [to] workspace back_and_forth'.
+ *
+ */
+void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
+    owindow *current;
+    Con *ws;
+
+    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);
+        return;
+    }
+
+    HANDLE_EMPTY_MATCH;
+
+    TAILQ_FOREACH(current, &owindows, owindows) {
+        DLOG("matching: %p / %s\n", current->con, current->con->name);
+        con_move_to_workspace(current->con, ws, true, false);
+    }
+
+    cmd_output->needs_tree_render = true;
+    // XXX: default reply for now, make this a better reply
+    ysuccess(true);
+}
+
 /*
  * Implementation of 'move [window|container] [to] workspace <name>'.
  *
@@ -432,6 +487,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
     /* get the workspace */
     Con *ws = workspace_get(name, NULL);
 
+    ws = maybe_auto_back_and_forth_workspace(ws);
+
     HANDLE_EMPTY_MATCH;
 
     TAILQ_FOREACH(current, &owindows, owindows) {
@@ -445,7 +502,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) {
 }
 
 /*
- * Implementation of 'move [window|container] [to] workspace number <number>'.
+ * Implementation of 'move [window|container] [to] workspace number <name>'.
  *
  */
 void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
@@ -469,8 +526,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
     if (parsed_num == LONG_MIN ||
         parsed_num == LONG_MAX ||
         parsed_num < 0 ||
-        *endptr != '\0') {
-        LOG("Could not parse \"%s\" as a number.\n", which);
+        endptr == which) {
+        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
         y(map_open);
         ystr("success");
         y(bool, false);
@@ -489,6 +546,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) {
         workspace = workspace_get(which, NULL);
     }
 
+    workspace = maybe_auto_back_and_forth_workspace(workspace);
+
     HANDLE_EMPTY_MATCH;
 
     TAILQ_FOREACH(current, &owindows, owindows) {
@@ -717,11 +776,11 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz
 }
 
 /*
- * Implementation of 'border normal|none|1pixel|toggle'.
+ * Implementation of 'border normal|none|1pixel|toggle|pixel'.
  *
  */
-void cmd_border(I3_CMD, char *border_style_str) {
-    DLOG("border style should be changed to %s\n", border_style_str);
+void cmd_border(I3_CMD, char *border_style_str, char *border_width ) {
+    DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width);
     owindow *current;
 
     HANDLE_EMPTY_MATCH;
@@ -729,23 +788,39 @@ void cmd_border(I3_CMD, char *border_style_str) {
     TAILQ_FOREACH(current, &owindows, owindows) {
         DLOG("matching: %p / %s\n", current->con, current->con->name);
         int border_style = current->con->border_style;
+        char *end;
+        int tmp_border_width = -1;
+        tmp_border_width = strtol(border_width, &end, 10);
+        if (end == border_width) {
+            /* no valid digits found */
+            tmp_border_width = -1;
+        }
         if (strcmp(border_style_str, "toggle") == 0) {
             border_style++;
             border_style %= 3;
+            if (border_style == BS_NORMAL)
+                tmp_border_width = 2;
+            else if (border_style == BS_NONE)
+                tmp_border_width = 0;
+            else if (border_style == BS_PIXEL)
+                tmp_border_width = 1;
         } else {
             if (strcmp(border_style_str, "normal") == 0)
                 border_style = BS_NORMAL;
-            else if (strcmp(border_style_str, "none") == 0)
+            else if (strcmp(border_style_str, "pixel") == 0)
+                border_style = BS_PIXEL;
+            else if (strcmp(border_style_str, "1pixel") == 0){
+                border_style = BS_PIXEL;
+                tmp_border_width = 1;
+            } else if (strcmp(border_style_str, "none") == 0)
                 border_style = BS_NONE;
-            else if (strcmp(border_style_str, "1pixel") == 0)
-                border_style = BS_1PIXEL;
             else {
                 ELOG("BUG: called with border_style=%s\n", border_style_str);
                 ysuccess(false);
                 return;
             }
         }
-        con_set_border_style(current->con, border_style);
+        con_set_border_style(current->con, border_style, tmp_border_width);
     }
 
     cmd_output->needs_tree_render = true;
@@ -807,7 +882,7 @@ void cmd_workspace(I3_CMD, char *which) {
 }
 
 /*
- * Implementation of 'workspace number <number>'
+ * Implementation of 'workspace number <name>'
  *
  */
 void cmd_workspace_number(I3_CMD, char *which) {
@@ -818,8 +893,8 @@ void cmd_workspace_number(I3_CMD, char *which) {
     if (parsed_num == LONG_MIN ||
         parsed_num == LONG_MAX ||
         parsed_num < 0 ||
-        *endptr != '\0') {
-        LOG("Could not parse \"%s\" as a number.\n", which);
+        endptr == which) {
+        LOG("Could not parse initial part of \"%s\" as a number.\n", which);
         y(map_open);
         ystr("success");
         y(bool, false);
@@ -838,8 +913,6 @@ void cmd_workspace_number(I3_CMD, char *which) {
     if (!workspace) {
         LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
         ysuccess(true);
-        /* terminate the which string after the endposition of the number */
-        *endptr = '\0';
         workspace_show_by_name(which);
         cmd_output->needs_tree_render = true;
         return;
@@ -949,13 +1022,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) {
 
     // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
     if (strcasecmp(name, "up") == 0)
-        output = get_output_next(D_UP, current_output);
+        output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "down") == 0)
-        output = get_output_next(D_DOWN, current_output);
+        output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "left") == 0)
-        output = get_output_next(D_LEFT, current_output);
+        output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT);
     else if (strcasecmp(name, "right") == 0)
-        output = get_output_next(D_RIGHT, current_output);
+        output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT);
     else
         output = get_output_by_name(name);
 
@@ -1042,8 +1115,12 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
         Con *content = output_get_content(output->con);
         LOG("got output %p with content %p\n", output, content);
 
+        Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head));
+        LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
+
         Con *ws = con_get_workspace(current->con);
         LOG("should move workspace %p / %s\n", ws, ws->name);
+        bool workspace_was_visible = workspace_is_visible(ws);
 
         if (con_num_children(ws->parent) == 1) {
             LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
@@ -1080,7 +1157,6 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
         }
 
         /* detach from the old output and attach to the new output */
-        bool workspace_was_visible = workspace_is_visible(ws);
         Con *old_content = ws->parent;
         con_detach(ws);
         if (workspace_was_visible) {
@@ -1102,6 +1178,11 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) {
             /* Focus the moved workspace on the destination output. */
             workspace_show(ws);
         }
+
+        /* Call the on_remove_child callback of the workspace which previously
+         * was visible on the destination output. Since it is no longer
+         * visible, it might need to get cleaned up. */
+        CALL(previously_visible_ws, on_remove_child);
     }
 
     cmd_output->needs_tree_render = true;
index f5ccfcddbed5b541566c12e80eb59cb7bfa9c404..0539c7ab6be526dfbbad725e38009dc9497b5060 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -28,6 +28,20 @@ char *colors[] = {
 
 static void con_on_remove_child(Con *con);
 
+/*
+ * force parent split containers to be redrawn
+ *
+ */
+static void con_force_split_parents_redraw(Con *con) {
+    Con *parent = con;
+
+    while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
+        if (parent->split)
+            FREE(parent->deco_render_params);
+        parent = parent->parent;
+    }
+}
+
 /*
  * Create a new container (and attach it to the given parent, if not NULL).
  * This function initializes the data structures and creates the appropriate
@@ -41,6 +55,7 @@ Con *con_new(Con *parent, i3Window *window) {
     new->type = CT_CON;
     new->window = window;
     new->border_style = config.default_border;
+    new->current_border_width = -1;
     static int cnt = 0;
     DLOG("opening window %d\n", cnt);
 
@@ -162,6 +177,7 @@ add_to_focus_head:
      * This way, we have the option to insert Cons without having
      * to focus them. */
     TAILQ_INSERT_TAIL(focus_head, con, focused);
+    con_force_split_parents_redraw(con);
 }
 
 /*
@@ -169,6 +185,7 @@ add_to_focus_head:
  *
  */
 void con_detach(Con *con) {
+    con_force_split_parents_redraw(con);
     if (con->type == CT_FLOATING_CON) {
         TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
         TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
@@ -195,8 +212,14 @@ void con_focus(Con *con) {
         con_focus(con->parent);
 
     focused = con;
-    if (con->urgent) {
+    /* We can't blindly reset non-leaf containers since they might have
+     * other urgent children. Therefore we only reset leafs and propagate
+     * the changes upwards via con_update_parents_urgency() which does proper
+     * checks before resetting the urgency.
+     */
+    if (con->urgent && con_is_leaf(con)) {
         con->urgent = false;
+        con_update_parents_urgency(con);
         workspace_update_urgent_flag(con_get_workspace(con));
     }
 }
@@ -337,6 +360,14 @@ Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
     return NULL;
 }
 
+/**
+ * Returns true if the container is internal, such as __i3_scratch
+ *
+ */
+bool con_is_internal(Con *con) {
+    return (con->name[0] == '_' && con->name[1] == '_');
+}
+
 /*
  * Returns true if the node is floating.
  *
@@ -670,7 +701,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
      * calling tree_render(), so for the "real" focus this is a no-op).
      * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and
      * we don’t focus when there is a fullscreen con on that workspace. */
-    if ((workspace->name[0] != '_' || workspace->name[1] != '_') &&
+    if (!con_is_internal(workspace) &&
         con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL)
         con_focus(con_descend_focused(con));
 
@@ -679,8 +710,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
      * don’t want to focus invisible workspaces */
     if (source_output != dest_output &&
         workspace_is_visible(workspace) &&
-        workspace->name[0] != '_' &&
-        workspace->name[1] != '_') {
+        !con_is_internal(workspace)) {
         DLOG("Moved to a different output, focusing target\n");
     } else {
         /* Descend focus stack in case focus_next is a workspace which can
@@ -939,52 +969,38 @@ Con *con_descend_direction(Con *con, direction_t direction) {
  */
 Rect con_border_style_rect(Con *con) {
     adjacent_t borders_to_hide = ADJ_NONE;
+    int border_width = con->current_border_width;
+    DLOG("The border width for con is set to: %d\n", con->current_border_width);
     Rect result;
+    if (con->current_border_width < 0)
+        border_width = config.default_border_width;
+    DLOG("Effective border width is set to: %d\n", border_width);
     /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */
     int border_style = con_border_style(con);
     if (border_style == BS_NONE)
         return (Rect){ 0, 0, 0, 0 };
     borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
-    switch (border_style) {
-    case BS_NORMAL:
-        result = (Rect){2, 0, -(2 * 2), -2};
-        if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
-            result.x -= 2;
-            result.width += 2;
-        }
-        if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
-            result.width += 2;
-        }
-        /* With normal borders we never hide the upper border */
-        if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
-            result.height += 2;
-        }
-        return result;
-
-    case BS_1PIXEL:
-        result = (Rect){1, 1, -2, -2};
-        if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
-            result.x -= 1;
-            result.width += 1;
-        }
-        if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
-            result.width += 1;
-        }
-        if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) {
-            result.y -= 1;
-            result.height += 1;
-        }
-        if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
-            result.height += 1;
-        }
-        return result;
-
-    case BS_NONE:
-        return (Rect){0, 0, 0, 0};
-
-    default:
-        assert(false);
+    if (border_style == BS_NORMAL) {
+        result = (Rect){border_width, 0 , -(2 * border_width), -(border_width)};
+    } else {
+        result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
+    }
+    if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
+        result.x -= border_width;
+        result.width += border_width;
+    }
+    if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) {
+        result.width += border_width;
+    }
+    if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE && (border_style != BS_NORMAL)) {
+        result.y -= border_width;
+        result.height += border_width;
     }
+    if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) {
+        result.height += border_width;
+    }
+    return result;
+
 }
 
 /*
@@ -1039,10 +1055,11 @@ int con_border_style(Con *con) {
  * floating window.
  *
  */
-void con_set_border_style(Con *con, int border_style) {
+void con_set_border_style(Con *con, int border_style, int border_width) {
     /* Handle the simple case: non-floating containerns */
     if (!con_is_floating(con)) {
         con->border_style = border_style;
+        con->current_border_width = border_width;
         return;
     }
 
@@ -1061,6 +1078,7 @@ void con_set_border_style(Con *con, int border_style) {
 
     /* Change the border style, get new border/decoration values. */
     con->border_style = border_style;
+    con->current_border_width = border_width;
     bsr = con_border_style_rect(con);
     int deco_height =
         (con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
@@ -1142,6 +1160,7 @@ void con_set_layout(Con *con, int layout) {
 
             tree_flatten(croot);
         }
+        con_force_split_parents_redraw(con);
         return;
     }
 
@@ -1162,6 +1181,7 @@ void con_set_layout(Con *con, int layout) {
     } else {
         con->layout = layout;
     }
+    con_force_split_parents_redraw(con);
 }
 
 /*
@@ -1241,6 +1261,8 @@ static void con_on_remove_child(Con *con) {
         return;
     }
 
+    con_force_split_parents_redraw(con);
+
     /* TODO: check if this container would swallow any other client and
      * don’t close it automatically. */
     int children = con_num_children(con);
@@ -1371,3 +1393,104 @@ bool con_fullscreen_permits_focusing(Con *con) {
     /* Focusing con would hide it behind a fullscreen window, disallow it. */
     return false;
 }
+
+/*
+ *
+ * Checks if the given container has an urgent child.
+ *
+ */
+bool con_has_urgent_child(Con *con) {
+    Con *child;
+
+    if (con_is_leaf(con))
+        return con->urgent;
+
+    /* We are not interested in floating windows since they can only be
+     * attached to a workspace → nodes_head instead of focus_head */
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        if (con_has_urgent_child(child))
+            return true;
+    }
+
+    return false;
+}
+
+/*
+ * Make all parent containers urgent if con is urgent or clear the urgent flag
+ * of all parent containers if there are no more urgent children left.
+ *
+ */
+void con_update_parents_urgency(Con *con) {
+    Con *parent = con->parent;
+
+    bool new_urgency_value = con->urgent;
+    while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
+        if (new_urgency_value) {
+            parent->urgent = true;
+        } else {
+            /* We can only reset the urgency when the parent
+             * has no other urgent children */
+            if (!con_has_urgent_child(parent))
+                parent->urgent = false;
+        }
+        parent = parent->parent;
+    }
+}
+
+/*
+ * Create a string representing the subtree under con.
+ *
+ */
+char *con_get_tree_representation(Con *con) {
+    /* this code works as follows:
+     *  1) create a string with the layout type (D/V/H/T/S) and an opening bracket
+     *  2) append the tree representation of the children to the string
+     *  3) add closing bracket
+     *
+     * The recursion ends when we hit a leaf, in which case we return the
+     * class_instance of the contained window.
+     */
+
+    /* end of recursion */
+    if (con_is_leaf(con)) {
+        if (!con->window)
+            return sstrdup("nowin");
+
+        if (!con->window->class_instance)
+            return sstrdup("noinstance");
+
+        return sstrdup(con->window->class_instance);
+    }
+
+    char *buf;
+    /* 1) add the Layout type to buf */
+    if (con->layout == L_DEFAULT)
+        buf = sstrdup("D[");
+    else if (con->layout == L_SPLITV)
+        buf = sstrdup("V[");
+    else if (con->layout == L_SPLITH)
+        buf = sstrdup("H[");
+    else if (con->layout == L_TABBED)
+        buf = sstrdup("T[");
+    else if (con->layout == L_STACKED)
+        buf = sstrdup("S[");
+
+    /* 2) append representation of children */
+    Con *child;
+    TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+        char *child_txt = con_get_tree_representation(child);
+
+        char *tmp_buf;
+        sasprintf(&tmp_buf, "%s%s%s", buf,
+                (TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt);
+        free(buf);
+        buf = tmp_buf;
+    }
+
+    /* 3) close the brackets */
+    char *complete_buf;
+    sasprintf(&complete_buf, "%s]", buf);
+    free(buf);
+
+    return complete_buf;
+}
index fcf3841ed3be21387a334deef437e974d4c82171..9e47d74b70330670cc9f0c6747ef8fff97e0ff79 100644 (file)
@@ -194,6 +194,13 @@ void switch_mode(const char *new_mode) {
         bindings = mode->bindings;
         translate_keysyms();
         grab_all_keys(conn, false);
+
+        char *event_msg;
+        sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name);
+
+        ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg);
+        FREE(event_msg);
+
         return;
     }
 
@@ -410,9 +417,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
 
     config.default_border = BS_NORMAL;
     config.default_floating_border = BS_NORMAL;
+    config.default_border_width = 2;
     /* Set default_orientation to NO_ORIENTATION for auto orientation. */
     config.default_orientation = NO_ORIENTATION;
 
+    /* Set default urgency reset delay to 500ms */
+    if (config.workspace_urgency_timer == 0)
+        config.workspace_urgency_timer = 0.5;
+
     parse_configuration(override_configpath);
 
     if (reload) {
index 5df39bf10342a189f5990db96d03c2fadb263b9e..d978c6438cc546738d40b9fdf8bcb787c8a3cd8d 100644 (file)
@@ -395,7 +395,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) {
     tree_render();
 
     /* Drag the window */
-    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
+    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event);
     tree_render();
 }
 
@@ -479,13 +479,21 @@ void floating_resize_window(Con *con, const bool proportional,
         corner |= BORDER_LEFT;
     else corner |= BORDER_RIGHT;
 
-    if (event->event_y <= (con->rect.height / 2))
+    int cursor = 0;
+    if (event->event_y <= (con->rect.height / 2)) {
         corner |= BORDER_TOP;
-    else corner |= BORDER_BOTTOM;
+        cursor = (corner & BORDER_LEFT) ?
+            XCURSOR_CURSOR_TOP_LEFT_CORNER : XCURSOR_CURSOR_TOP_RIGHT_CORNER;
+    }
+    else {
+        corner |= BORDER_BOTTOM;
+        cursor = (corner & BORDER_LEFT) ?
+            XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER;
+    }
 
     struct resize_window_callback_params params = { corner, proportional, event };
 
-    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
+    drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, &params);
 }
 
 /*
@@ -497,13 +505,16 @@ void floating_resize_window(Con *con, const bool proportional,
  *
  */
 void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
-                confine_to, border_t border, callback_t callback, const void *extra)
+                confine_to, border_t border, int cursor, callback_t callback, const void *extra)
 {
     uint32_t new_x, new_y;
     Rect old_rect = { 0, 0, 0, 0 };
     if (con != NULL)
         memcpy(&old_rect, &(con->rect), sizeof(Rect));
 
+    Cursor xcursor = (cursor && xcursor_supported) ?
+        xcursor_get_cursor(cursor) : XCB_NONE;
+
     /* Grab the pointer */
     xcb_grab_pointer_cookie_t cookie;
     xcb_grab_pointer_reply_t *reply;
@@ -514,7 +525,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t
         XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */
         XCB_GRAB_MODE_ASYNC, /* keyboard mode */
         confine_to,          /* confine_to = in which window should the cursor stay */
-        XCB_NONE,            /* don’t display a special cursor */
+        xcursor,             /* possibly display a special cursor */
         XCB_CURRENT_TIME);
 
     if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) {
index 21a873420d989430394f7d1b25043ea76ca6e100..7fa29e126f0944cfdf0d821c7975bc05c0a4c2fe 100644 (file)
@@ -836,7 +836,13 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
     }
 
     /* Update the flag on the client directly */
-    con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
+    bool hint_urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
+
+    if (con->urgency_timer == NULL) {
+        con->urgent = hint_urgent;
+    } else
+        DLOG("Discarding urgency WM_HINT because timer is running\n");
+
     //CLIENT_LOG(con);
     if (con->window) {
         if (con->urgent) {
@@ -846,6 +852,9 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
             con->window->urgent.tv_usec = 0;
         }
     }
+
+    con_update_parents_urgency(con);
+
     LOG("Urgency flag changed to %d\n", con->urgent);
 
     Con *ws;
index 7dfbc8713c19c9e44295441f880bf21350f7f504..84ef2c36289adc63736c74583d4b0bd49e6cbcaf 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -52,7 +52,7 @@ static bool mkdirp(const char *path) {
         ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
         return false;
     }
-    char *copy = strdup(path);
+    char *copy = sstrdup(path);
     /* strip trailing slashes, if any */
     while (copy[strlen(copy)-1] == '/')
         copy[strlen(copy)-1] = '\0';
@@ -266,11 +266,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
         case BS_NONE:
             ystr("none");
             break;
-        case BS_1PIXEL:
-            ystr("1pixel");
+        case BS_PIXEL:
+            ystr("pixel");
             break;
     }
 
+    ystr("current_border_width");
+    y(integer, con->current_border_width);
+
     dump_rect(gen, "rect", con->rect);
     dump_rect(gen, "window_rect", con->window_rect);
     dump_rect(gen, "geometry", con->geometry);
@@ -405,7 +408,7 @@ IPC_HANDLER(get_workspaces) {
 
     Con *output;
     TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
-        if (output->name[0] == '_' && output->name[1] == '_')
+        if (con_is_internal(output))
             continue;
         Con *ws;
         TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
index cce1a7127da9f660d4282e52cb8d521e743d6fa3..d57358857938eb0fbd83d0ab34fc6531594a75e9 100644 (file)
@@ -183,8 +183,11 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) {
             sasprintf(&buf, "%.*s", (int)len, val);
             if (strcasecmp(buf, "none") == 0)
                 json_node->border_style = BS_NONE;
-            else if (strcasecmp(buf, "1pixel") == 0)
-                json_node->border_style = BS_1PIXEL;
+            else if (strcasecmp(buf, "1pixel") == 0) {
+                json_node->border_style = BS_PIXEL;
+                json_node->current_border_width = 1;
+            } else if (strcasecmp(buf, "pixel") == 0)
+                json_node->border_style = BS_PIXEL;
             else if (strcasecmp(buf, "normal") == 0)
                 json_node->border_style = BS_NORMAL;
             else LOG("Unhandled \"border\": %s\n", buf);
@@ -278,6 +281,9 @@ static int json_int(void *ctx, long val) {
     if (strcasecmp(last_key, "num") == 0)
         json_node->num = val;
 
+    if (strcasecmp(last_key, "current_border_width") == 0)
+        json_node->current_border_width = val;
+
     if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
         json_node->old_id = val;
 
@@ -350,11 +356,22 @@ void tree_append_json(const char *filename) {
     /* TODO: percent of other windows are not correctly fixed at the moment */
     FILE *f;
     if ((f = fopen(filename, "r")) == NULL) {
-        LOG("Cannot open file\n");
+        LOG("Cannot open file \"%s\"\n", filename);
+        return;
+    }
+    struct stat stbuf;
+    if (fstat(fileno(f), &stbuf) != 0) {
+        LOG("Cannot fstat() the file\n");
+        fclose(f);
+        return;
+    }
+    char *buf = smalloc(stbuf.st_size);
+    int n = fread(buf, 1, stbuf.st_size, f);
+    if (n != stbuf.st_size) {
+        LOG("File \"%s\" could not be read entirely, not loading.\n", filename);
+        fclose(f);
         return;
     }
-    char *buf = malloc(65535); /* TODO */
-    int n = fread(buf, 1, 65535, f);
     LOG("read %d bytes\n", n);
     yajl_gen g;
     yajl_handle hand;
index 8bae9957db894e3fac02363b37e857ef49f9ad4f..3b29f7a8c5c05d3c7746ecedee0d7ab634936b78 100644 (file)
@@ -233,12 +233,9 @@ static void i3_exit(void) {
  *
  */
 static void handle_signal(int sig, siginfo_t *info, void *data) {
-    fprintf(stderr, "Received signal %d, terminating\n", sig);
     if (*shmlogname != '\0') {
-        fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname);
         shm_unlink(shmlogname);
     }
-    fflush(stderr);
     raise(sig);
 }
 
@@ -542,6 +539,7 @@ int main(int argc, char *argv[]) {
 
     uint32_t mask = XCB_CW_EVENT_MASK;
     uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
+                          XCB_EVENT_MASK_BUTTON_PRESS |
                           XCB_EVENT_MASK_STRUCTURE_NOTIFY |         /* when the user adds a screen (e.g. video
                                                                            projector), the root window gets a
                                                                            ConfigureNotify */
index 8b6ba1d9ac52a13a1e87423330fc50de038a0710..a73a94c92450ad9c9052481694aa7f1e6b032f48 100644 (file)
@@ -101,89 +101,76 @@ Output *get_output_containing(int x, int y) {
  *
  */
 Output *get_output_most(direction_t direction, Output *current) {
-    Output *output, *candidate = NULL;
-    int position = 0;
-    TAILQ_FOREACH(output, &outputs, outputs) {
-        if (!output->active)
-            continue;
-
-        /* Repeated calls of WIN determine the winner of the comparison */
-        #define WIN(variable, condition) \
-            if (variable condition) { \
-                candidate = output; \
-                position = variable; \
-            } \
-            break;
-
-        if (((direction == D_UP) || (direction == D_DOWN)) &&
-            (current->rect.x != output->rect.x))
-            continue;
-
-        if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
-            (current->rect.y != output->rect.y))
-            continue;
-
-        switch (direction) {
-            case D_UP:
-                WIN(output->rect.y, <= position);
-            case D_DOWN:
-                WIN(output->rect.y, >= position);
-            case D_LEFT:
-                WIN(output->rect.x, <= position);
-            case D_RIGHT:
-                WIN(output->rect.x, >= position);
-        }
-    }
-
-    assert(candidate != NULL);
-
-    return candidate;
+    Output *best = get_output_next(direction, current, FARTHEST_OUTPUT);
+    if (!best)
+        best = current;
+    DLOG("current = %s, best = %s\n", current->name, best->name);
+    return best;
 }
 
 /*
  * Gets the output which is the next one in the given direction.
  *
  */
-Output *get_output_next(direction_t direction, Output *current) {
-    Output *output, *candidate = NULL;
-
+Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) {
+    Rect *cur = &(current->rect),
+         *other;
+    Output *output,
+           *best = NULL;
     TAILQ_FOREACH(output, &outputs, outputs) {
         if (!output->active)
             continue;
 
-        if (((direction == D_UP) || (direction == D_DOWN)) &&
-            (current->rect.x != output->rect.x))
+        other = &(output->rect);
+
+        if ((direction == D_RIGHT && other->x > cur->x) ||
+            (direction == D_LEFT  && other->x < cur->x)) {
+            /* Skip the output when it doesn’t overlap the other one’s y
+             * coordinate at all. */
+            if ((other->y + other->height) <= cur->y ||
+                (cur->y   + cur->height)   <= other->y)
+                continue;
+        } else if ((direction == D_DOWN && other->y > cur->y) ||
+                   (direction == D_UP   && other->y < cur->y)) {
+            /* Skip the output when it doesn’t overlap the other one’s x
+             * coordinate at all. */
+            if ((other->x + other->width) <= cur->x ||
+                (cur->x   + cur->width)   <= other->x)
+                continue;
+        } else
             continue;
 
-        if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
-            (current->rect.y != output->rect.y))
+        /* No candidate yet? Start with this one. */
+        if (!best) {
+            best = output;
             continue;
+        }
 
-        switch (direction) {
-            case D_UP:
-                if (output->rect.y < current->rect.y &&
-                    (!candidate || output->rect.y < candidate->rect.y))
-                    candidate = output;
-                break;
-            case D_DOWN:
-                if (output->rect.y > current->rect.y &&
-                    (!candidate || output->rect.y > candidate->rect.y))
-                    candidate = output;
-                break;
-            case D_LEFT:
-                if (output->rect.x < current->rect.x &&
-                    (!candidate || output->rect.x > candidate->rect.x))
-                    candidate = output;
-                break;
-            case D_RIGHT:
-                if (output->rect.x > current->rect.x &&
-                    (!candidate || output->rect.x < candidate->rect.x))
-                    candidate = output;
-                break;
+        if (close_far == CLOSEST_OUTPUT) {
+            /* Is this output better (closer to the current output) than our
+             * current best bet? */
+            if ((direction == D_RIGHT && other->x < best->rect.x) ||
+                (direction == D_LEFT  && other->x > best->rect.x) ||
+                (direction == D_DOWN  && other->y < best->rect.y) ||
+                (direction == D_UP    && other->y > best->rect.y)) {
+                best = output;
+                continue;
+            }
+        } else {
+            /* Is this output better (farther to the current output) than our
+             * current best bet? */
+            if ((direction == D_RIGHT && other->x > best->rect.x) ||
+                (direction == D_LEFT  && other->x < best->rect.x) ||
+                (direction == D_DOWN  && other->y > best->rect.y) ||
+                (direction == D_UP    && other->y < best->rect.y)) {
+                best = output;
+                continue;
+            }
         }
     }
 
-    return candidate;
+    DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL"));
+    return best;
 }
 
 /*
index da993a57206ee13fa130616093dbc1e88fd91f38..369273c81c46df056dc4ebc77db1c048444fdb50 100644 (file)
@@ -225,7 +225,7 @@ void render_con(Con *con, bool render_fullscreen) {
 
     if (con->layout == L_OUTPUT) {
         /* Skip i3-internal outputs */
-        if (con->name[0] == '_' && con->name[1] == '_')
+        if (con_is_internal(con))
             return;
         render_l_output(con);
     } else if (con->type == CT_ROOT) {
@@ -240,32 +240,19 @@ void render_con(Con *con, bool render_fullscreen) {
          * windows/containers so that they overlap on another output. */
         DLOG("Rendering floating windows:\n");
         TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             /* Get the active workspace of that output */
             Con *content = output_get_content(output);
             Con *workspace = TAILQ_FIRST(&(content->focus_head));
 
-            /* Check for (floating!) fullscreen nodes */
+            /* Check for fullscreen nodes */
             /* XXX: This code duplication is unfortunate. Keep in mind to fix
              * this when we clean up the whole render.c */
             Con *fullscreen = NULL;
             fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
-            if (fullscreen) {
-                /* Either the fullscreen window is inside the floating
-                 * container, then we need to render and raise it now… */
-                if (con_inside_floating(fullscreen)) {
-                    fullscreen->rect = output->rect;
-                    x_raise_con(fullscreen);
-                    render_con(fullscreen, true);
+            if (fullscreen)
                     continue;
-                } else {
-                    /* …or it’s a tiling window, in which case the floating
-                     * windows should not overlap it, so we skip rendering this
-                     * output. */
-                    continue;
-                }
-            }
 
             Con *child;
             TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
@@ -324,7 +311,7 @@ void render_con(Con *con, bool render_fullscreen) {
             child->deco_rect.width = child->rect.width;
             child->deco_rect.height = deco_height;
 
-            if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
+            if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
                 child->rect.y += (deco_height * children);
                 child->rect.height -= (deco_height * children);
             }
@@ -341,12 +328,12 @@ void render_con(Con *con, bool render_fullscreen) {
             child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width;
             child->deco_rect.y = y - con->rect.y;
 
-            if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) {
+            if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
                 child->rect.y += deco_height;
                 child->rect.height -= deco_height;
                 child->deco_rect.height = deco_height;
             } else {
-                child->deco_rect.height = (child->border_style == BS_1PIXEL ? 1 : 0);
+                child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0);
             }
         }
 
index b65344a276c5edbefdee04f260e0856d45ca9441..268dc3fbd119b69780bcbee89f7d47cc195829d2 100644 (file)
@@ -106,7 +106,7 @@ 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, resize_callback, &params);
+    drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, &params);
 
     xcb_destroy_window(conn, helpwin);
     xcb_destroy_window(conn, grabwin);
index 16e26ceebe32b0964380b039e8a710fabe9274c8..7b30909554040360233317dd7f70bdeedb9ff28b 100644 (file)
@@ -39,11 +39,17 @@ void scratchpad_move(Con *con) {
         return;
     }
 
-    /* 1: Ensure the window is floating. From now on, we deal with the
-     * CT_FLOATING_CON. We use automatic == false because the user made the
-     * choice that this window should be a scratchpad (and floating). */
-    floating_enable(con, false);
-    con = con->parent;
+    /* 1: Ensure the window or any parent is floating. From now on, we deal
+     * with the CT_FLOATING_CON. We use automatic == false because the user
+     * made the choice that this window should be a scratchpad (and floating).
+     */
+    Con *maybe_floating_con = con_inside_floating(con);
+    if (maybe_floating_con == NULL) {
+        floating_enable(con, false);
+        con = con->parent;
+    } else {
+        con = maybe_floating_con;
+    }
 
     /* 2: Send the window to the __i3_scratch workspace, mainting its
      * coordinates and not warping the pointer. */
index 321bc78a5951b2b9fa13c78819301f53049b5d49..3d598d50ab8014410ad9a5c38eb956564f98cd6a 100644 (file)
@@ -255,6 +255,15 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
     x_con_kill(con);
 
     con_detach(con);
+
+    /* disable urgency timer, if needed */
+    if (con->urgency_timer != NULL) {
+        DLOG("Removing urgency timer of con %p\n", con);
+        workspace_update_urgent_flag(con_get_workspace(con));
+        ev_timer_stop(main_loop, con->urgency_timer);
+        FREE(con->urgency_timer);
+    }
+
     if (con->type != CT_FLOATING_CON) {
         /* If the container is *not* floating, we might need to re-distribute
          * percentage values for the resized containers. */
@@ -477,7 +486,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap)
         else
             return false;
 
-        next_output = get_output_next(direction, current_output);
+        next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT);
         if (!next_output)
             return false;
         DLOG("Next output is %s\n", next_output->name);
index 94efd47b6e2455cf0151f7a41b0366d9022aeed8..3a5844cb8c1794d1adbe50e6e713046d582739d0 100644 (file)
@@ -311,12 +311,30 @@ static void workspace_reassign_sticky(Con *con) {
         workspace_reassign_sticky(current);
 }
 
+/*
+ * Callback to reset the urgent flag of the given con to false. May be started by
+ * _workspace_show to avoid urgency hints being lost by switching to a workspace
+ * focusing the con.
+ *
+ */
+static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
+    Con *con = w->data;
+
+    DLOG("Resetting urgency flag of con %p by timer\n", con);
+    con->urgent = false;
+    con_update_parents_urgency(con);
+    workspace_update_urgent_flag(con_get_workspace(con));
+    tree_render();
+
+    ev_timer_stop(main_loop, con->urgency_timer);
+    FREE(con->urgency_timer);
+}
 
 static void _workspace_show(Con *workspace) {
     Con *current, *old = NULL;
 
     /* safe-guard against showing i3-internal workspaces like __i3_scratch */
-    if (workspace->name[0] == '_' && workspace->name[1] == '_')
+    if (con_is_internal(workspace))
         return;
 
     /* disable fullscreen for the other workspaces and get the workspace we are
@@ -350,6 +368,43 @@ static void _workspace_show(Con *workspace) {
     LOG("switching to %p\n", workspace);
     Con *next = con_descend_focused(workspace);
 
+    /* Memorize current output */
+    Con *old_output = con_get_output(focused);
+
+    /* Display urgency hint for a while if the newly visible workspace would
+     * focus and thereby immediately destroy it */
+    if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) {
+        /* focus for now… */
+        con_focus(next);
+
+        /* … but immediately reset urgency flags; they will be set to false by
+         * the timer callback in case the container is focused at the time of
+         * its expiration */
+        focused->urgent = true;
+        workspace->urgent = true;
+
+        if (focused->urgency_timer == NULL) {
+            DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
+                    focused, workspace);
+            focused->urgency_timer = scalloc(sizeof(struct ev_timer));
+            /* use a repeating timer to allow for easy resets */
+            ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb,
+                    config.workspace_urgency_timer, config.workspace_urgency_timer);
+            focused->urgency_timer->data = focused;
+            ev_timer_start(main_loop, focused->urgency_timer);
+        } else {
+            DLOG("Resetting urgency timer of con %p on workspace %p\n",
+                    focused, workspace);
+            ev_timer_again(main_loop, focused->urgency_timer);
+        }
+    } else
+        con_focus(next);
+
+    /* Close old workspace if necessary. This must be done *after* doing
+     * urgency handling, because tree_close() will do a con_focus() on the next
+     * client, which will clear the urgency flag too early. Also, there is no
+     * way for con_focus() to know about when to clear urgency immediately and
+     * when to defer it. */
     if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
         /* check if this workspace is currently visible */
         if (!workspace_is_visible(old)) {
@@ -359,10 +414,6 @@ static void _workspace_show(Con *workspace) {
         }
     }
 
-    /* Memorize current output */
-    Con *old_output = con_get_output(focused);
-
-    con_focus(next);
     workspace->fullscreen_mode = CF_OUTPUT;
     LOG("focused now = %p / %s\n", focused, focused->name);
 
@@ -392,8 +443,7 @@ void workspace_show(Con *workspace) {
  */
 void workspace_show_by_name(const char *num) {
     Con *workspace;
-    bool changed_num_workspaces;
-    workspace = workspace_get(num, &changed_num_workspaces);
+    workspace = workspace_get(num, NULL);
     _workspace_show(workspace);
 }
 
@@ -413,7 +463,7 @@ Con* workspace_next(void) {
         /* If currently a numbered workspace, find next numbered workspace. */
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -434,7 +484,7 @@ Con* workspace_next(void) {
         bool found_current = false;
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -453,7 +503,7 @@ Con* workspace_next(void) {
     if (!next) {
         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -485,7 +535,7 @@ Con* workspace_prev(void) {
         /* If numbered workspace, find previous numbered workspace. */
         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE || child->num == -1)
@@ -504,7 +554,7 @@ Con* workspace_prev(void) {
         bool found_current = false;
         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -523,7 +573,7 @@ Con* workspace_prev(void) {
     if (!prev) {
         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
             /* Skip outputs starting with __, they are internal. */
-            if (output->name[0] == '_' && output->name[1] == '_')
+            if (con_is_internal(output))
                 continue;
             NODES_FOREACH_REVERSE(output_get_content(output)) {
                 if (child->type != CT_WORKSPACE)
@@ -664,6 +714,22 @@ void workspace_back_and_forth(void) {
     workspace_show_by_name(previous_workspace_name);
 }
 
+/*
+ * Returns the previously focused workspace con, or NULL if unavailable.
+ *
+ */
+Con *workspace_back_and_forth_get(void) {
+    if (!previous_workspace_name) {
+        DLOG("no previous workspace name set.");
+        return NULL;
+    }
+
+    Con *workspace;
+    workspace = workspace_get(previous_workspace_name, NULL);
+
+    return workspace;
+}
+
 static bool get_urgency_flag(Con *con) {
     Con *child;
     TAILQ_FOREACH(child, &(con->nodes_head), nodes)
diff --git a/src/x.c b/src/x.c
index 7cb28c1abc984a85ac7fc8ed152bd59299d0605d..afce40fe643e602e82a0431731d546eb3d38614b 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -431,7 +431,7 @@ void x_draw_decoration(Con *con) {
             xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline);
         }
         /* 1pixel border needs an additional line at the top */
-        if (p->border_style == BS_1PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
+        if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
             xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y };
             xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline);
         }
@@ -467,12 +467,20 @@ void x_draw_decoration(Con *con) {
     /* 5: draw two unconnected lines in border color */
     xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border });
     Rect *dr = &(con->deco_rect);
+    int deco_diff_l = 2;
+    int deco_diff_r = 2;
+    if (parent->layout == L_TABBED) {
+        if (TAILQ_PREV(con, nodes_head, nodes) != NULL)
+            deco_diff_l = 0;
+        if (TAILQ_NEXT(con, nodes) != NULL)
+            deco_diff_r = 0;
+    }
     xcb_segment_t segments[] = {
         { dr->x,                 dr->y,
           dr->x + dr->width - 1, dr->y },
 
-        { dr->x + 2,             dr->y + dr->height - 1,
-          dr->x + dr->width - 3, dr->y + dr->height - 1 }
+        { dr->x + deco_diff_l,                 dr->y + dr->height - 1,
+          dr->x - deco_diff_r + dr->width - 1, dr->y + dr->height - 1 }
     };
     xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments);
 
@@ -481,16 +489,27 @@ void x_draw_decoration(Con *con) {
     int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
 
     struct Window *win = con->window;
-    if (win == NULL || win->name == NULL) {
-        /* this is a non-leaf container, we need to make up a good description */
-        // TODO: use a good description instead of just "another container"
-        draw_text_ascii("another container",
+    if (win == NULL) {
+        /* we have a split container which gets a representation
+         * of its children as title
+         */
+        char *title;
+        char *tree = con_get_tree_representation(con);
+        sasprintf(&title, "i3: %s", tree);
+        free(tree);
+
+        draw_text_ascii(title,
                 parent->pixmap, parent->pm_gc,
                 con->deco_rect.x + 2, con->deco_rect.y + text_offset_y,
                 con->deco_rect.width - 2);
+        free(title);
+
         goto copy_pixmaps;
     }
 
+    if (win->name == NULL)
+        goto copy_pixmaps;
+
     int indent_level = 0,
         indent_mult = 0;
     Con *il_parent = parent;
index 7683b0d37ed8fd76c0330200b4650440ea8b53ab..90fd69dd237a67cc7470f238af33a693e6175aad 100644 (file)
@@ -34,10 +34,15 @@ static Cursor load_cursor(const char *name) {
 }
 
 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_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");
 }
 
 /*
index b1e698ae9182395efcd68f7f4f4edb1d9ee9fd31..b522fc309e6349adabfaa2dfe62df9a654d37cee 100755 (executable)
@@ -8,7 +8,7 @@ WriteMakefile(
     MIN_PERL_VERSION => '5.010000', # 5.10.0
     PREREQ_PM => {
         'AnyEvent'     => 0,
-        'AnyEvent::I3' => '0.09',
+        'AnyEvent::I3' => '0.14',
         'X11::XCB'     => '0.03',
         'Inline'       => 0,
         'ExtUtils::PkgConfig' => 0,
index 5ea9d0783777d8f02f265eb7a83375f30a4426b3..d73bb3327d242344f641930d8e34534d5ec725f5 100755 (executable)
@@ -66,6 +66,22 @@ my $result = GetOptions(
 
 pod2usage(-verbose => 2, -exitcode => 0) if $help;
 
+# Check for missing executables
+my @binaries = qw(
+                   ../i3
+                   ../i3bar/i3bar
+                   ../i3-config-wizard/i3-config-wizard
+                   ../i3-dump-log/i3-dump-log
+                   ../i3-input/i3-input
+                   ../i3-msg/i3-msg
+                   ../i3-nagbar/i3-nagbar
+               );
+
+foreach my $binary (@binaries) {
+    die "$binary executable not found" unless -e $binary;
+    die "$binary is not an executable" unless -x $binary;
+}
+
 @displays = split(/,/, join(',', @displays));
 @displays = map { s/ //g; $_ } @displays;
 
index 10368532eeae60c3eb8431efcc354e83f195cf45..ff44e0ea0bc263ed52d29b0a789212c97ae1809f 100644 (file)
 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
 #   (unless you are already familiar with Perl)
 
-use i3test;
+use i3test i3_autostart => 0;
 use List::Util qw(first);
 
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+force_display_urgency_hint 0ms
+EOT
+my $pid = launch_with_config($config);
+
 my $tmp = fresh_workspace;
 
 #####################################################################
@@ -145,4 +153,84 @@ is($x->input_focus, $bottom->id, 'oldest urgent window focused');
 $bottom->delete_hint('urgency');
 sync_with_i3;
 
+################################################################################
+# Check if urgent flag gets propagated to parent containers
+################################################################################
+
+cmd 'split v';
+
+
+
+sub count_urgent {
+    my ($con) = @_;
+
+    my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}});
+    my $urgent = grep { $_->{urgent} } @children;
+    $urgent += count_urgent($_) for @children;
+    return $urgent;
+}
+
+$tmp = fresh_workspace;
+
+my $win1 = open_window;
+my $win2 = open_window;
+cmd 'layout stacked';
+cmd 'split vertical';
+my $win3 = open_window;
+my $win4 = open_window;
+cmd 'split horizontal' ;
+my $win5 = open_window;
+my $win6 = open_window;
+
+sync_with_i3;
+
+
+my $urgent = count_urgent(get_ws($tmp));
+is($urgent, 0, 'no window got the urgent flag');
+
+cmd '[id="' . $win2->id . '"] focus';
+sync_with_i3;
+$win5->add_hint('urgency');
+$win6->add_hint('urgency');
+sync_with_i3;
+
+# we should have 5 urgent cons. win5, win6 and their 3 split parents.
+
+$urgent = count_urgent(get_ws($tmp));
+is($urgent, 5, '2 windows and 3 split containers got the urgent flag');
+
+cmd '[id="' . $win5->id . '"] focus';
+sync_with_i3;
+
+# now win5 and still the split parents should be urgent.
+$urgent = count_urgent(get_ws($tmp));
+is($urgent, 4, '1 window and 3 split containers got the urgent flag');
+
+cmd '[id="' . $win6->id . '"] focus';
+sync_with_i3;
+
+# now now window should be urgent.
+$urgent = count_urgent(get_ws($tmp));
+is($urgent, 0, 'All urgent flags got cleared');
+
+################################################################################
+# Regression test: Check that urgent floating containers work properly (ticket
+# #821)
+################################################################################
+
+$tmp = fresh_workspace;
+my $floating_win = open_floating_window;
+
+# switch away
+fresh_workspace;
+
+$floating_win->add_hint('urgency');
+sync_with_i3;
+
+cmd "workspace $tmp";
+
+does_i3_live;
+
+exit_gracefully($pid);
+
 done_testing;
index 70008801261c078d7433cd1784e729670e7146f2..fc0b742a80de93b6519cbd79d056c8bddcbaf01c 100644 (file)
@@ -69,6 +69,7 @@ my $expected = {
     border => 'normal',
     'floating_nodes' => $ignore,
     workspace_layout => 'default',
+    current_border_width => -1,
 };
 
 # a shallow copy is sufficient, since we only ignore values at the root
index 7991abe54458d9ef2a1d6375270414b956ce07c5..2283ddc1370591c767ba496a2b9dfbd8660b136b 100644 (file)
@@ -156,6 +156,34 @@ ok(!workspace_exists('5'), 'workspace 5 does not exist');
 cmd 'workspace number 5';
 ok(workspace_exists('5'), 'workspace 5 was created');
 
+################################################################################
+# Check that we can go to workspace "7: foo" with the command
+# "workspace number 7: bar", i.e. the additional workspace name is ignored.
+################################################################################
+
+ok(!workspace_exists('7'), 'workspace 7 does not exist');
+ok(!workspace_exists('7: bar'), 'workspace 7: bar does not exist');
+ok(!workspace_exists('7: foo'), 'workspace 7: foo does not exist yet');
+cmd 'workspace 7: foo';
+ok(workspace_exists('7: foo'), 'workspace 7: foo was created');
+cmd 'open';
+
+cmd 'workspace 6';
+ok(workspace_exists('7: foo'), 'workspace 7: foo still open');
+cmd 'workspace number 7: bar';
+is(focused_ws(), '7: foo', 'now on workspace 7: foo');
+ok(!workspace_exists('7'), 'workspace 7 still does not exist');
+ok(!workspace_exists('7: bar'), 'workspace 7: bar still does not exist');
+
+################################################################################
+# Check that "workspace number 8: foo" will create workspace "8: foo" if it
+# does not yet exist (just like "workspace 8: foo" would).
+################################################################################
+
+ok(!workspace_exists('8: foo'), 'workspace 8: foo does not exist');
+cmd 'workspace number 8: foo';
+ok(workspace_exists('8: foo'), 'workspace 8: foo was created');
+
 ################################################################################
 # Verify that renaming workspaces works.
 ################################################################################
index ba26c85ff122955de3d27067efba8ac1663d9241..a4f6b6082ad7da6f4a4ebef73241aad0ba402899 100644 (file)
@@ -79,6 +79,28 @@ is_num_children('12', 0, 'no container on 12 anymore');
 
 ok(!workspace_exists('13'), 'workspace 13 does still not exist');
 
+################################################################################
+# Check that 'move to workspace number <number><name>' works to move a window to
+# named workspaces which start with <number>.
+################################################################################
+
+cmd 'workspace 15: meh';
+cmd 'open';
+is_num_children('15: meh', 1, 'one container on 15: meh');
+
+ok(!workspace_exists('15'), 'workspace 15 does not exist yet');
+ok(!workspace_exists('15: duh'), 'workspace 15: duh does not exist yet');
+
+cmd 'workspace 14';
+cmd 'open';
+
+cmd 'move to workspace number 15: duh';
+is_num_children('15: meh', 2, 'two containers on 15: meh');
+is_num_children('14', 0, 'no container on 14 anymore');
+
+ok(!workspace_exists('15'), 'workspace 15 does still not exist');
+ok(!workspace_exists('15: duh'), 'workspace 15 does still not exist');
+
 ###################################################################
 # check if 'move workspace next' and 'move workspace prev' work
 ###################################################################
index 1db64575800258e798e826ed3ebd557ef30b02f0..de1b7c81f35feb1c107015acee8acc89f86dbc85 100644 (file)
@@ -36,13 +36,13 @@ is(get_border_style(), 'normal', 'border style normal');
 
 cmd 'border 1pixel';
 
-is(get_border_style(), '1pixel', 'border style 1pixel after changing');
+is(get_border_style(), 'pixel', 'border style 1pixel after changing');
 
 # perform an inplace-restart
 cmd 'restart';
 
 does_i3_live;
 
-is(get_border_style(), '1pixel', 'border style still 1pixel after restart');
+is(get_border_style(), 'pixel', 'border style still 1pixel after restart');
 
 done_testing;
index 7377194daacb891015bee32acdc9d237c0c1fe7d..c89dcc764b9313877e4c087dcd722f36d514904e 100644 (file)
@@ -28,26 +28,32 @@ is($nodes[0]->{border}, 'normal', 'border style normal');
 
 cmd 'border 1pixel';
 @nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
 
 cmd 'border none';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'none', 'border style none');
+is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
 
 cmd 'border normal';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'normal', 'border style back to normal');
+is($nodes[0]->{current_border_width}, 2, 'border width = 2px');
 
 cmd 'border toggle';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'none', 'border style none');
+is($nodes[0]->{current_border_width}, 0, 'border width = 0px');
 
 cmd 'border toggle';
 @nodes = @{get_ws_content($tmp)};
-is($nodes[0]->{border}, '1pixel', 'border style 1pixel');
+is($nodes[0]->{border}, 'pixel', 'border style 1pixel');
+is($nodes[0]->{current_border_width}, 1, 'border width = 1px');
 
 cmd 'border toggle';
 @nodes = @{get_ws_content($tmp)};
 is($nodes[0]->{border}, 'normal', 'border style back to normal');
+is($nodes[0]->{current_border_width}, 2, 'border width = 2px');
 
 done_testing;
index 6e837cf09528e07a0f1773dc8e7598bb8cfaafd4..56ad865a6657ea1c6d21c2c38aff2a3e3f92859a 100644 (file)
@@ -65,7 +65,8 @@ $first = open_window;
 
 @content = @{get_ws_content($tmp)};
 ok(@content == 1, 'one container opened');
-is($content[0]->{border}, '1pixel', 'border normal by default');
+is($content[0]->{border}, 'pixel', 'border pixel by default');
+is($content[0]->{current_border_width}, -1, 'border width pixels -1 (default)');
 
 exit_gracefully($pid);
 
@@ -119,7 +120,7 @@ $wscontent = get_ws($tmp);
 @floating = @{$wscontent->{floating_nodes}};
 ok(@floating == 1, 'one floating container opened');
 $floatingcon = $floating[0];
-is($floatingcon->{nodes}->[0]->{border}, '1pixel', 'border normal by default');
+is($floatingcon->{nodes}->[0]->{border}, 'pixel', 'border pixel by default');
 
 exit_gracefully($pid);
 
index 07c3c84a1e8fe8c016ad0e68bb7bf33c2a229921..8ed33030818343e595db4cfcf9df558ad79b60b8 100644 (file)
@@ -47,6 +47,33 @@ ok(get_ws($second_ws)->{focused}, 'second workspace focused');
 cmd qq|workspace "$second_ws"|;
 ok(get_ws($second_ws)->{focused}, 'second workspace still focused');
 
+################################################################################
+# verify that 'move workspace back_and_forth' works as expected
+################################################################################
+
+cmd qq|workspace "$first_ws"|;
+my $first_win = open_window;
+
+cmd qq|workspace "$second_ws"|;
+my $second_win = open_window;
+
+is(@{get_ws_content($first_ws)}, 1, 'one container on ws 1 before moving');
+cmd 'move workspace back_and_forth';
+is(@{get_ws_content($first_ws)}, 2, 'two containers on ws 1 before moving');
+
+my $third_win = open_window;
+
+################################################################################
+# verify that moving to the current ws is a no-op without
+# workspace_auto_back_and_forth.
+################################################################################
+
+cmd qq|workspace "$first_ws"|;
+
+is(@{get_ws_content($second_ws)}, 1, 'one container on ws 2 before moving');
+cmd qq|move workspace "$first_ws"|;
+is(@{get_ws_content($second_ws)}, 1, 'still one container');
+
 exit_gracefully($pid);
 
 #####################################################################
@@ -72,6 +99,19 @@ ok(get_ws($third_ws)->{focused}, 'third workspace focused');
 
 cmd qq|workspace "$third_ws"|;
 ok(get_ws($second_ws)->{focused}, 'second workspace focused');
+$first_win = open_window;
+
+################################################################################
+# verify that moving to the current ws moves to the previous one with
+# workspace_auto_back_and_forth.
+################################################################################
+
+cmd qq|workspace "$first_ws"|;
+$second_win = open_window;
+
+is(@{get_ws_content($second_ws)}, 1, 'one container on ws 2 before moving');
+cmd qq|move workspace "$first_ws"|;
+is(@{get_ws_content($second_ws)}, 2, 'two containers on ws 2');
 
 ################################################################################
 # Now see if "workspace number <number>" also works as expected with
index 87bda5295242c077de5941d5065237b8a09488a7..dafe51e07efca0827f8b7831e9b253a702baa997 100644 (file)
@@ -323,39 +323,51 @@ does_i3_live;
 # 11: focus a workspace and move all of its children to the scratchpad area
 ################################################################################
 
-$tmp = fresh_workspace;
+sub verify_scratchpad_move_multiple_win {
+    my $floating = shift;
 
-my $first = open_window;
-my $second = open_window;
+    my $first = open_window;
+    my $second = open_window;
 
-cmd 'focus parent';
-cmd 'move scratchpad';
+    if ($floating) {
+        cmd 'floating toggle';
+        cmd 'focus tiling';
+    }
 
-does_i3_live;
+    cmd 'focus parent';
+    cmd 'move scratchpad';
 
-$ws = get_ws($tmp);
-is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
-is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');
+    does_i3_live;
 
-# show the first window.
-cmd 'scratchpad show';
+    $ws = get_ws($tmp);
+    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+    is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');
 
-$ws = get_ws($tmp);
-is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
-is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+    # show the first window.
+    cmd 'scratchpad show';
 
-$old_focus = get_focused($tmp);
+    $ws = get_ws($tmp);
+    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
 
-cmd 'scratchpad show';
+    $old_focus = get_focused($tmp);
 
-# show the second window.
-cmd 'scratchpad show';
+    cmd 'scratchpad show';
 
-$ws = get_ws($tmp);
-is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
-is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+    # show the second window.
+    cmd 'scratchpad show';
 
-isnt(get_focused($tmp), $old_focus, 'focus changed');
+    $ws = get_ws($tmp);
+    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
+    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
+
+    isnt(get_focused($tmp), $old_focus, 'focus changed');
+}
+
+$tmp = fresh_workspace;
+verify_scratchpad_move_multiple_win(0);
+$tmp = fresh_workspace;
+verify_scratchpad_move_multiple_win(1);
 
 # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
 
index 8f732bacdf20a5754476e2cfa6dc67828dd89083..2448452ea290ff9e0c4036ddd3d260452d61e63e 100644 (file)
@@ -1,5 +1,19 @@
 #!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)
+#
 # When using a command which moves a window to scratchpad from an invisible
 # (e.g. unfocused) workspace and immediately shows that window again, i3
 # crashed.
diff --git a/testcases/t/199-ipc-mode-event.t b/testcases/t/199-ipc-mode-event.t
new file mode 100644 (file)
index 0000000..43f7b17
--- /dev/null
@@ -0,0 +1,57 @@
+#!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)
+#
+# Verifies that the IPC 'mode' event is sent when modes are changed.
+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
+
+mode "m1" {
+    bindsym Mod1+x nop foo
+}
+
+mode "with spaces" {
+    bindsym Mod1+y nop bar
+}
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path(0));
+$i3->connect->recv;
+
+my $cv = AnyEvent->condvar;
+
+$i3->subscribe({
+    mode => sub {
+        my ($event) = @_;
+        $cv->send($event->{change} eq 'm1');
+    }
+})->recv;
+
+cmd 'mode "m1"';
+
+# Timeout after 0.5s
+my $t;
+$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
+
+ok($cv->recv, 'Mode event received');
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/200-urgency-timer.t b/testcases/t/200-urgency-timer.t
new file mode 100644 (file)
index 0000000..730a950
--- /dev/null
@@ -0,0 +1,149 @@
+#!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 whether the urgency timer works as expected and does not break
+# urgency handling.
+#
+
+use List::Util qw(first);
+use i3test i3_autostart => 0;
+use Time::HiRes qw(sleep);
+
+# Ensure the pointer is at (0, 0) so that we really start on the first
+# (the left) workspace.
+$x->root->warp_pointer(0, 0);
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+force_display_urgency_hint 150ms
+EOT
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Initial setup: one window on ws1, empty ws2
+#####################################################################
+
+my $tmp1 = fresh_workspace;
+my $w = open_window;
+
+my $tmp2 = fresh_workspace;
+cmd "workspace $tmp2";
+
+$w->add_hint('urgency');
+sync_with_i3;
+
+#######################################################################
+# Create a window on ws1, then switch to ws2, set urgency, switch back
+#######################################################################
+
+isnt($x->input_focus, $w->id, 'window not focused');
+
+my @content = @{get_ws_content($tmp1)};
+my @urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, "window marked as urgent");
+
+# switch to ws1
+cmd "workspace $tmp1";
+
+# this will start the timer
+sleep(0.1);
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window still marked as urgent');
+
+# now check if the timer was triggered
+cmd "workspace $tmp2";
+
+sleep(0.1);
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 0, 'window not marked as urgent anymore');
+
+#######################################################################
+# Create another window on ws1, focus it, switch to ws2, make the other
+# window urgent, and switch back. This should not trigger the timer.
+#######################################################################
+
+cmd "workspace $tmp1";
+my $w2 = open_window;
+is($x->input_focus, $w2->id, 'window 2 focused');
+
+cmd "workspace $tmp2";
+$w->add_hint('urgency');
+sync_with_i3;
+
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window 1 marked as urgent');
+
+# Switch back to ws1. This should focus w2.
+cmd "workspace $tmp1";
+is($x->input_focus, $w2->id, 'window 2 focused');
+
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window 1 still marked as urgent');
+
+# explicitly focusing the window should result in immediate urgency reset
+cmd '[id="' . $w->id . '"] focus';
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 0, 'window 1 not marked as urgent anymore');
+
+################################################################################
+# open a stack, mark one window as urgent, switch to that workspace and verify
+# it’s cleared correctly.
+################################################################################
+
+sub count_total_urgent {
+    my ($con) = @_;
+
+    my $urgent = ($con->{urgent} ? 1 : 0);
+    $urgent += count_total_urgent($_) for (@{$con->{nodes}}, @{$con->{floating_nodes}});
+    return $urgent;
+}
+
+my $tmp3 = fresh_workspace;
+open_window;
+open_window;
+cmd 'split v';
+my $split_left = open_window;
+cmd 'layout stacked';
+
+cmd "workspace $tmp2";
+
+is(count_total_urgent(get_ws($tmp3)), 0, "no urgent windows on workspace $tmp3");
+
+$split_left->add_hint('urgency');
+sync_with_i3;
+
+cmp_ok(count_total_urgent(get_ws($tmp3)), '>=', 0, "more than one urgent window on workspace $tmp3");
+
+cmd "workspace $tmp3";
+
+# Remove the urgency hint.
+$split_left->delete_hint('urgency');
+sync_with_i3;
+
+sleep(0.2);
+is(count_total_urgent(get_ws($tmp3)), 0, "no more urgent windows on workspace $tmp3");
+
+exit_gracefully($pid);
+
+done_testing;
index 7a976271c12ce64ed0c4a232ba0890c1cde178ea..c087f9f5ad117946b493383a5bbd893bf84922e3 100644 (file)
@@ -133,6 +133,26 @@ is($old_rect->{y}, $new_rect->{y}, 'y coordinate unchanged');
 is($old_rect->{width}, $new_rect->{width}, 'width unchanged');
 is($old_rect->{height}, $new_rect->{height}, 'height unchanged');
 
+################################################################################
+# Verify that empty workspaces get cleaned up when moving a different workspace
+# to that output.
+################################################################################
+
+my $empty_ws = fresh_workspace(output => 0);
+my $other_output_ws = fresh_workspace(output => 1);
+cmd 'open';
+
+($x0, $x1) = workspaces_per_screen();
+ok($empty_ws ~~ @$x0, 'empty_ws on fake-0');
+ok($other_output_ws ~~ @$x1, 'other_output_ws on fake-1');
+
+cmd 'move workspace to output left';
+
+($x0, $x1) = workspaces_per_screen();
+ok(!($empty_ws ~~ @$x0), 'empty_ws not on fake-0');
+ok(!($empty_ws ~~ @$x1), 'empty_ws not on fake-1');
+ok($other_output_ws ~~ @$x0, 'other_output_ws on fake-0');
+
 exit_gracefully($pid);
 
 done_testing;
diff --git a/testcases/t/506-focus-right.t b/testcases/t/506-focus-right.t
new file mode 100644 (file)
index 0000000..3f58b00
--- /dev/null
@@ -0,0 +1,179 @@
+#!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)
+#
+# Verifies that focus output right works with monitor setups that don’t line up
+# on their x/y coordinates.
+#
+# ticket #771, bug still present in commit dd743f3b55b2f86d9f1f69ef7952ae8ece4de504
+#
+use i3test i3_autostart => 0;
+
+sub test_focus_left_right {
+    my ($config) = @_;
+
+    my $pid = launch_with_config($config);
+
+    my $i3 = i3(get_socket_path(0));
+
+    $x->root->warp_pointer(0, 0);
+    sync_with_i3;
+
+    ############################################################################
+    # Ensure that moving left and right works.
+    ############################################################################
+
+    # First ensure both workspaces have something to focus
+    cmd "workspace 1";
+    my $left_win = open_window;
+
+    cmd "workspace 2";
+    my $right_win = open_window;
+
+    is($x->input_focus, $right_win->id, 'right window focused');
+
+    cmd "focus output left";
+    is($x->input_focus, $left_win->id, 'left window focused');
+
+    cmd "focus output right";
+    is($x->input_focus, $right_win->id, 'right window focused');
+
+    cmd "focus output right";
+    is($x->input_focus, $left_win->id, 'left window focused (wrapping)');
+
+    cmd "focus output left";
+    is($x->input_focus, $right_win->id, 'right window focused (wrapping)');
+
+    ############################################################################
+    # Ensure that moving down/up from S0 doesn’t crash i3 and is a no-op.
+    ############################################################################
+
+    my $second = fresh_workspace(output => 1);
+    my $third_win = open_window;
+
+    cmd "focus output down";
+    is($x->input_focus, $third_win->id, 'right window still focused');
+
+    cmd "focus output up";
+    is($x->input_focus, $third_win->id, 'right window still focused');
+
+    does_i3_live;
+
+    exit_gracefully($pid);
+}
+
+# Screen setup looks like this:
+# +----+
+# |    |--------+
+# | S1 |   S2   |
+# |    |--------+
+# +----+
+#
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x1080+1080+500
+EOT
+
+test_focus_left_right($config);
+
+# Screen setup looks like this:
+# +----+--------+
+# |    |   S2   |
+# | S1 |--------+
+# |    |
+# +----+
+#
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x200+1080+0
+EOT
+
+test_focus_left_right($config);
+
+# Screen setup looks like this:
+# +----+
+# |    |
+# | S1 |--------+
+# |    |   S2   |
+# +----+--------+
+#
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x200+1080+1720
+EOT
+
+test_focus_left_right($config);
+
+# Screen setup looks like this:
+# +----+        +----+
+# |    |        |    |
+# | S1 |--------+ S3 |
+# |    |   S2   |    |
+# +----+--------+----+
+#
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1080x1920+0+0,1920x200+1080+1720,1080x1920+1280+0
+EOT
+
+my $pid = launch_with_config($config);
+
+my $i3 = i3(get_socket_path(0));
+
+$x->root->warp_pointer(0, 0);
+sync_with_i3;
+
+############################################################################
+# Ensure that focusing right/left works in the expected order.
+############################################################################
+
+sub get_focused_output {
+    my $tree = i3(get_socket_path())->get_tree->recv;
+    my ($focused_id) = @{$tree->{focus}};
+    my ($output) = grep { $_->{id} == $focused_id } @{$tree->{nodes}};
+    return $output->{name};
+}
+
+is(get_focused_output(), 'fake-0', 'focus on fake-0');
+
+cmd 'focus output right';
+is(get_focused_output(), 'fake-1', 'focus on fake-1');
+
+cmd 'focus output right';
+is(get_focused_output(), 'fake-2', 'focus on fake-2');
+
+cmd 'focus output left';
+is(get_focused_output(), 'fake-1', 'focus on fake-1');
+
+cmd 'focus output left';
+is(get_focused_output(), 'fake-0', 'focus on fake-0');
+
+cmd 'focus output left';
+is(get_focused_output(), 'fake-2', 'focus on fake-2 (wrapping)');
+
+cmd 'focus output right';
+is(get_focused_output(), 'fake-0', 'focus on fake-0 (wrapping)');
+
+exit_gracefully($pid);
+
+done_testing;